velloo 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -40
- package/canvas/assets/index-ClaBqcaS.js +28 -0
- package/canvas/assets/lucide-all-iIF-pmzN.js +1 -0
- package/canvas/index.html +1 -1
- package/{chunk-k6ferbam.js → chunk-08sv3k41.js} +1 -1
- package/{chunk-74skmg76.js → chunk-0fe83yzt.js} +1 -1
- package/{chunk-n5007jcz.js → chunk-1h55s5he.js} +1 -1
- package/{chunk-5js0qw32.js → chunk-2c7jbx46.js} +1 -1
- package/chunk-308ynsxd.js +2 -0
- package/{chunk-w5rzk6ms.js → chunk-3e8vez4d.js} +2 -2
- package/{chunk-bk8b7f9a.js → chunk-40xgcth8.js} +4 -3
- package/chunk-4dn75m6t.js +3 -0
- package/chunk-4fj5tahv.js +3 -0
- package/chunk-6nqghjda.js +3 -0
- package/chunk-6w6ch4ce.js +2 -0
- package/chunk-735ezz23.js +86 -0
- package/chunk-7eq7nx37.js +7 -0
- package/chunk-7eyb8yrf.js +6 -0
- package/{chunk-8ssr8a8w.js → chunk-80fjfyha.js} +3 -6
- package/{chunk-b7j86xq7.js → chunk-80r93t2e.js} +1 -1
- package/chunk-94vx1yb0.js +3 -0
- package/{chunk-g02nd9hn.js → chunk-a84n72m2.js} +1 -1
- package/chunk-ampmdzz4.js +10 -0
- package/chunk-avns4dbf.js +2 -0
- package/{chunk-kxk5x7g6.js → chunk-ayrb3qnm.js} +1 -1
- package/{chunk-fgw3hp2v.js → chunk-bwnvcncx.js} +1 -1
- package/chunk-ccszg02q.js +5 -0
- package/{chunk-jnsfgtn9.js → chunk-cvn689d5.js} +1 -1
- package/{chunk-65wb0g7d.js → chunk-dg6z4vdd.js} +1 -1
- package/chunk-dgwjpbp9.js +3 -0
- package/{chunk-hs2j6s1v.js → chunk-dy41n60j.js} +8 -31
- package/{chunk-e2ag33vd.js → chunk-ek71z2fv.js} +2 -2
- package/{chunk-h0c36khx.js → chunk-er1fxb7d.js} +1 -1
- package/{chunk-xr6f94rj.js → chunk-eyr8hrzf.js} +1 -1
- package/{chunk-56vqqr8s.js → chunk-fsbsksga.js} +1 -1
- package/{chunk-t2ttkxe9.js → chunk-hcb68zt7.js} +1 -1
- package/chunk-j11fyeqh.js +2 -0
- package/{chunk-d385435m.js → chunk-jb21jkp1.js} +1 -1
- package/{chunk-7e39xe21.js → chunk-jfx9xjmr.js} +1 -1
- package/{chunk-b2ebm1t3.js → chunk-jr0ss0mm.js} +1 -1
- package/chunk-jv0xggh0.js +2 -0
- package/chunk-k17gfwq3.js +2 -0
- package/chunk-mh2pr7cq.js +5 -0
- package/chunk-mnapdp1y.js +2 -0
- package/chunk-n9gs6yx9.js +2 -0
- package/chunk-ngnymg2q.js +3 -0
- package/{chunk-79ykzywe.js → chunk-pjtg3wnm.js} +1 -1
- package/{chunk-2z0k59hz.js → chunk-pp86v6m6.js} +1 -1
- package/chunk-r22epxz2.js +3 -0
- package/chunk-rgasy862.js +5 -0
- package/{chunk-7p7ha0qk.js → chunk-rpwpeph5.js} +1 -1
- package/{chunk-k784090q.js → chunk-sjgqnykd.js} +1 -1
- package/chunk-sn3x2m0m.js +6 -0
- package/{chunk-mq4znqv2.js → chunk-t448rrfr.js} +1 -1
- package/{chunk-329e3phg.js → chunk-tdsk5wxa.js} +25 -25
- package/{chunk-2vx420g2.js → chunk-v40fn08f.js} +1 -1
- package/chunk-vdkrbzh1.js +2 -0
- package/{chunk-1gx45384.js → chunk-vgmr98cv.js} +7 -8
- package/chunk-vsv8z729.js +6 -0
- package/chunk-x4f4k9pp.js +26 -0
- package/{chunk-cep3ktr3.js → chunk-xt5ayg7g.js} +1 -1
- package/{chunk-rksq3w72.js → chunk-ydhwv29b.js} +1 -1
- package/chunk-zb6hc1y0.js +2 -0
- package/cli.js +1 -1
- package/launcher.cjs +1 -1
- package/package.json +3 -3
- package/pkgs/schema/src/config.ts +8 -1
- package/pkgs/schema/src/index.ts +8 -0
- package/pkgs/schema/src/migrate.ts +57 -4
- package/pkgs/schema/src/repo.ts +98 -16
- package/pkgs/schema/src/svg-sanitize.ts +472 -59
- package/skills/velloo-setup/SKILL.md +1 -1
- package/canvas/assets/index-BcBJ11d2.js +0 -28
- package/canvas/assets/lucide-all-BdEyryfF.js +0 -1
- package/chunk-1bywxgc5.js +0 -4
- package/chunk-1y50g9vp.js +0 -2
- package/chunk-1yhc252b.js +0 -3
- package/chunk-2mv65qxs.js +0 -5
- package/chunk-40657ggd.js +0 -10
- package/chunk-582y60bv.js +0 -2
- package/chunk-5x173xay.js +0 -2
- package/chunk-75wbh438.js +0 -2
- package/chunk-7dggmqwq.js +0 -53
- package/chunk-946pcnt5.js +0 -5
- package/chunk-aapybh30.js +0 -6
- package/chunk-bjnhhsak.js +0 -2
- package/chunk-dt03awr1.js +0 -2
- package/chunk-erwfk47h.js +0 -3
- package/chunk-nfr3q7mg.js +0 -5
- package/chunk-nsqs1bqf.js +0 -2
- package/chunk-pphgrsxa.js +0 -2
- package/chunk-qpvj6kzq.js +0 -6
- package/chunk-qwv7xkms.js +0 -2
- package/chunk-tmtzxcs5.js +0 -2
- package/chunk-y0zxzefg.js +0 -5
- package/chunk-zf3xc1wz.js +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{
|
|
2
|
+
import{clone,normalizeParams,own,installLazyProp,NEVER,$constructor,parse,parseAsync,safeParse,safeParseAsync,$ZodType,$ZodObject,_coercedNumber,toJSONSchema,ZodError,string,url,ZodNumber,number,boolean,_null,any,unknown,array,object,strictObject,looseObject,union,discriminatedUnion,intersection,record,_enum,literal,ZodOptional,optional,custom,preprocess,datetime,exports_iso,AnnotationSchema,CanvasNoteSchema,AssetsFileSchema,EMPTY_ASSETS_FILE,MAX_BOARD_NAME_LENGTH,BoardSchema,isArchived,CommentAnchorSchema,CommentAuthorSchema,CommentThreadSchema,ExtensionPropDescriptorSchema,ExtensionSchema,designNameIssue,isDesignName,toDesignName,RepoManifestSchema,REPO_MANIFEST_FILE,normalizeRepoManifest,CURRENT_SCHEMA_VERSION,schemaVersionOf,APP_PATH_PREFIX,ViewportPresetSchema,ConfigSchema,isCssIdent,sanitizeCssTokenValue,neutralizeCssText,sanitizeGoogleFontSpec,pascalizeIconName,isComponentNode,isSnippetInstance,isParamRef,NodeIdSchema,nodeId,ScreenSchema,SnippetSchema,resolveSnippetArgs,substituteSnippetParams,applySnippetOverrides,applySnippetExtraClassName,svgLooksActive,sanitizeSvgMarkup,ColorsSchema,resolveColors,ThemeSchema,DEFAULT_TYPESET_NAME,isTypesetName,TYPESET_SCALE_NAMES,resolveHeadingLevel,headingClasses,textClasses,headingInlineStyle,textInlineStyle,typesetSafelist,typesetUtilityClasses,typesetV3FontSize,typesetThemeTokens,resolveTypeset,typesetCss,typesetScale,findDuplicateIds,ViewportSchema}from"./chunk-40xgcth8.js";import{require_react_development,buildBoardComposite,CHROMIUM_INSTALL_CMD,isCaptureTimeout,sessionStatePath,captureDir,listCaptures,readCaptureManifest,deleteCapture,isSafeCaptureId,isSafeCaptureFile,diffPngs,downscalePng,resizePng,pngSize,cropPng,sideBySidePng,unionRegion,HeadedBrowserMissingError,startCaptureSession,UnknownComponentError,resolveSnippetBody,resolveSnippetBodyForEdit,snippetParamPlaceholder,screenshotCompareBuffer,pdfPageBuffer,pdfDeckBuffer,RenderGuardLimitError,collectSerializedRefs,serializeTree,renderBodyGuarded,renderScreen,screenshotBuffer,captureScreenshot,measureRendered,captureUrlScreenshot,classifyCapture}from"./chunk-3e8vez4d.js";import{ok,err,map,tryCatchAsync,$,DoAsync,isCloudErrorCode,IntentCatalogResponseSchema,GenerateResponseSchema,FeedbackIssuerResponseSchema,FeedbackSignaturesResponseSchema,unreachable,loggedOut,protocolViolation,insecureCloudUrl,invalidRequest,readFailure,cloudFailure,describeCloudError,cloudJson,cloudFetch,LocatorSchema,AddNodeBody,normalizeAddNode,updatePropsShape,UpdatePropsBody,ApplyClassesBody,removeNodeShape,RemoveNodeBody,moveNodeShape,MoveNodeBody,setNodeIdShape,SetNodeIdBody,addScreenShape,AddScreenBody,removeScreenShape,RemoveScreenBody,SetScreenTreeBody,updateScreenShape,addBoardShape,AddBoardBody,updateBoardShape,UpdateBoardBody,removeBoardShape,RemoveBoardBody,reorderBoardsShape,ReorderBoardsBody,AddBoardGroupBody,UpdateBoardGroupBody,RemoveBoardGroupBody,ReorderBoardGroupsBody,addFrameShape,AddFrameBody,updateFrameShape,UpdateFrameBody,removeFrameShape,RemoveFrameBody,MoveFrameBody,addSnippetShape,AddSnippetBody,updateSnippetShape,UpdateSnippetBody,removeSnippetShape,RemoveSnippetBody,InstantiateSnippetBody,UpdateSnippetArgsBody,updateSnippetInstanceShape,UpdateSnippetInstanceBody,normalizeUpdateSnippetInstance,AddAnnotationBody,UpdateAnnotationBody,RemoveAnnotationBody,AddNoteBody,UpdateNoteBody,RemoveNoteBody,updateViewportPresetsShape,UpdateViewportPresetsBody,UpdateDefaultsBody,UpdateCodegenBody,UpdateDesignNameBody,UpdateFeedbackBody}from"./chunk-t448rrfr.js";import{__toESM,__commonJS,__require}from"./chunk-2smsg2ct.js";var require_react_jsx_dev_runtime_development=__commonJS(function(exports){var React=__toESM(require_react_development());(function(){function getComponentNameFromType(type){if(type==null)return null;if(typeof type==="function")return type.$$typeof===REACT_CLIENT_REFERENCE?null:type.displayName||type.name||null;if(typeof type==="string")return type;switch(type){case REACT_FRAGMENT_TYPE:return"Fragment";case REACT_PROFILER_TYPE:return"Profiler";case REACT_STRICT_MODE_TYPE:return"StrictMode";case REACT_SUSPENSE_TYPE:return"Suspense";case REACT_SUSPENSE_LIST_TYPE:return"SuspenseList";case REACT_ACTIVITY_TYPE:return"Activity"}if(typeof type==="object")switch(typeof type.tag==="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),type.$$typeof){case REACT_PORTAL_TYPE:return"Portal";case REACT_CONTEXT_TYPE:return type.displayName||"Context";case REACT_CONSUMER_TYPE:return(type._context.displayName||"Context")+".Consumer";case REACT_FORWARD_REF_TYPE:var innerType=type.render;return type=type.displayName,type||(type=innerType.displayName||innerType.name||"",type=type!==""?"ForwardRef("+type+")":"ForwardRef"),type;case REACT_MEMO_TYPE:return innerType=type.displayName||null,innerType!==null?innerType:getComponentNameFromType(type.type)||"Memo";case REACT_LAZY_TYPE:innerType=type._payload,type=type._init;try{return getComponentNameFromType(type(innerType))}catch(x){}}return null}function testStringCoercion(value){return""+value}function checkKeyStringCoercion(value){try{testStringCoercion(value);var JSCompiler_inline_result=!1}catch(e){JSCompiler_inline_result=!0}if(JSCompiler_inline_result){JSCompiler_inline_result=console;var JSCompiler_temp_const=JSCompiler_inline_result.error,JSCompiler_inline_result$jscomp$0=typeof Symbol==="function"&&Symbol.toStringTag&&value[Symbol.toStringTag]||value.constructor.name||"Object";return JSCompiler_temp_const.call(JSCompiler_inline_result,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",JSCompiler_inline_result$jscomp$0),testStringCoercion(value)}}function getTaskName(type){if(type===REACT_FRAGMENT_TYPE)return"<>";if(typeof type==="object"&&type!==null&&type.$$typeof===REACT_LAZY_TYPE)return"<...>";try{var name=getComponentNameFromType(type);return name?"<"+name+">":"<...>"}catch(x){return"<...>"}}function getOwner(){var dispatcher=ReactSharedInternals.A;return dispatcher===null?null:dispatcher.getOwner()}function UnknownOwner(){return Error("react-stack-top-frame")}function hasValidKey(config){if(hasOwnProperty.call(config,"key")){var getter=Object.getOwnPropertyDescriptor(config,"key").get;if(getter&&getter.isReactWarning)return!1}return config.key!==void 0}function defineKeyPropWarningGetter(props,displayName){function warnAboutAccessingKey(){specialPropKeyWarningShown||(specialPropKeyWarningShown=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",displayName))}warnAboutAccessingKey.isReactWarning=!0,Object.defineProperty(props,"key",{get:warnAboutAccessingKey,configurable:!0})}function elementRefGetterWithDeprecationWarning(){var componentName=getComponentNameFromType(this.type);return didWarnAboutElementRef[componentName]||(didWarnAboutElementRef[componentName]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),componentName=this.props.ref,componentName!==void 0?componentName:null}function ReactElement(type,key,props,owner,debugStack,debugTask){var refProp=props.ref;return type={$$typeof:REACT_ELEMENT_TYPE,type,key,props,_owner:owner},(refProp!==void 0?refProp:null)!==null?Object.defineProperty(type,"ref",{enumerable:!1,get:elementRefGetterWithDeprecationWarning}):Object.defineProperty(type,"ref",{enumerable:!1,value:null}),type._store={},Object.defineProperty(type._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(type,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(type,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:debugStack}),Object.defineProperty(type,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:debugTask}),Object.freeze&&(Object.freeze(type.props),Object.freeze(type)),type}function jsxDEVImpl(type,config,maybeKey,isStaticChildren,debugStack,debugTask){var children=config.children;if(children!==void 0)if(isStaticChildren)if(isArrayImpl(children)){for(isStaticChildren=0;isStaticChildren<children.length;isStaticChildren++)validateChildKeys(children[isStaticChildren]);Object.freeze&&Object.freeze(children)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else validateChildKeys(children);if(hasOwnProperty.call(config,"key")){children=getComponentNameFromType(type);var keys=Object.keys(config).filter(function(k){return k!=="key"});isStaticChildren=0<keys.length?"{key: someKey, "+keys.join(": ..., ")+": ...}":"{key: someKey}",didWarnAboutKeySpread[children+isStaticChildren]||(keys=0<keys.length?"{"+keys.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
3
3
|
let props = %s;
|
|
4
4
|
<%s {...props} />
|
|
5
5
|
React keys must be passed directly to JSX without using spread:
|
|
@@ -47,7 +47,7 @@ ${pad}</${closeTag}>`)}var DEFAULT_ALIAS="@/components/ui";function pascal(input
|
|
|
47
47
|
${stepLines.join(`
|
|
48
48
|
`)}
|
|
49
49
|
${indent}}`)}return blocks.join(`
|
|
50
|
-
`)}function appendAnimations(animation,lines){if(!animation)return;for(let[name,value]of Object.entries(animation)){if(!isCssIdent(name))continue;lines.push(` --animate-${name}: ${sanitizeCssTokenValue(value)};`)}}function appendColorBlock(colors,lines){for(let{key,pair}of COLOR_SLOTS){let value=colors[key];if(value===void 0)continue;let def=defaultOf(value);if(def!==void 0)lines.push(` --color-${key}: ${sanitizeCssTokenValue(def)};`);if(pair){let fg=fgOf(value);if(fg!==void 0)lines.push(` --color-${key}-foreground: ${sanitizeCssTokenValue(fg)};`)}}}function emitGlobalsCss(theme,opts={}){let lines=[];lines.push("/**"),lines.push(` * Generated by velloo from theme "${sanitizeCssTokenValue(theme.name)}".`),lines.push(" * Hand-edits are welcome; re-running `velloo theme
|
|
50
|
+
`)}function appendAnimations(animation,lines){if(!animation)return;for(let[name,value]of Object.entries(animation)){if(!isCssIdent(name))continue;lines.push(` --animate-${name}: ${sanitizeCssTokenValue(value)};`)}}function appendColorBlock(colors,lines){for(let{key,pair}of COLOR_SLOTS){let value=colors[key];if(value===void 0)continue;let def=defaultOf(value);if(def!==void 0)lines.push(` --color-${key}: ${sanitizeCssTokenValue(def)};`);if(pair){let fg=fgOf(value);if(fg!==void 0)lines.push(` --color-${key}-foreground: ${sanitizeCssTokenValue(fg)};`)}}}function emitGlobalsCss(theme,opts={}){let lines=[];lines.push("/**"),lines.push(` * Generated by velloo from theme "${sanitizeCssTokenValue(theme.name)}".`),lines.push(" * Hand-edits are welcome; re-running `velloo theme export` shows a diff."),lines.push(" */");let googleFonts=theme.typography.googleFonts;if(googleFonts&&googleFonts.length>0){let families=googleFonts.map((f)=>`family=${sanitizeGoogleFontSpec(f)}`).join("&");lines.push(`@import url("https://fonts.googleapis.com/css2?${families}&display=swap");`)}lines.push('@import "tailwindcss";'),lines.push(`@import "./${TYPESET_CSS_FILENAME}";`),lines.push("");for(let pattern of typesetSafelist())lines.push(`@source inline("${pattern}");`);lines.push(""),lines.push("@theme {");let radiusBase=theme.radius.md??theme.radius.lg??theme.radius.sm;if(radiusBase!==void 0){let v=typeof radiusBase==="number"?`${radiusBase}px`:sanitizeCssTokenValue(radiusBase);lines.push(` --radius: ${v};`)}let fonts=theme.typography.fontFamily;if(fonts){let noteEmitted=!1;for(let[role,stack]of Object.entries(fonts)){if(!isCssIdent(role))continue;if(lines.push(` --font-${role}: ${sanitizeCssTokenValue(stack)};`),!noteEmitted&&opts.fontLoadingNote!==!1&&(!googleFonts||googleFonts.length===0))lines.push(" /* TODO: load these fonts via next/font or a <link> in your <head>. */"),noteEmitted=!0}}lines.push(...typesetThemeTokens()),appendColorBlock(theme.colors,lines),appendPalette(theme.palette,lines),appendSpacing(theme.spacing,lines),appendShadows(theme.shadows,lines),appendAnimations(theme.animation,lines);let keyframesCss=keyframesToCss(theme.keyframes," ");if(keyframesCss)lines.push(keyframesCss);if(lines.push("}"),lines.push(""),theme.colorsDark||theme.paletteDark)lines.push("@custom-variant dark (&:is(.dark *));"),lines.push(""),lines.push(".dark {"),appendColorBlock(theme.colorsDark??{},lines),appendPalette(theme.paletteDark,lines),lines.push("}"),lines.push("");lines.push("@layer base {"),lines.push(" * {"),lines.push(" border-color: var(--color-border);"),lines.push(" }"),lines.push(" body {"),lines.push(" background-color: var(--color-background);"),lines.push(" color: var(--color-foreground);"),lines.push(" font-family: var(--font-sans);"),lines.push(" }"),lines.push("}"),lines.push("");let container=theme.container;if(container&&(container.center||container.padding||container.maxWidth)){if(lines.push("@utility container {"),lines.push(" width: 100%;"),container.center)lines.push(" margin-inline: auto;");if(container.padding)lines.push(` padding-inline: ${sanitizeCssTokenValue(container.padding)};`);if(container.maxWidth)lines.push(` max-width: ${sanitizeCssTokenValue(container.maxWidth)};`);lines.push("}"),lines.push("")}if(opts.customCss&&opts.customCss.trim()!=="")lines.push("/* === velloo theme/custom.css === */"),lines.push(neutralizeCssText(opts.customCss.trimEnd())),lines.push("");return lines.join(`
|
|
51
51
|
`)}var DEFAULT_CONTENT_GLOBS=["./app/**/*.{ts,tsx}","./components/**/*.{ts,tsx}","./pages/**/*.{ts,tsx}"];function emitTailwindConfig(contentGlobs=DEFAULT_CONTENT_GLOBS){return`import type { Config } from "tailwindcss";
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -60,7 +60,7 @@ ${contentGlobs.map((g)=>` ${JSON.stringify(g)},`).join(`
|
|
|
60
60
|
`)}
|
|
61
61
|
],
|
|
62
62
|
} satisfies Config;
|
|
63
|
-
`}import{existsSync}from"fs";import{mkdir as mkdir2,writeFile as writeFile2}from"fs/promises";import{basename,dirname as dirname2,join as join3}from"path";var toRgb=converter_default("rgb"),toHsl=converter_default("hsl"),clamp01=(n)=>Math.min(1,Math.max(0,n)),round4=(n)=>Math.round(n*1e4)/1e4;function pct(n){return`${Number((clamp01(n)*100).toFixed(1))}%`}function hslTriplet(value){let parsed=parse_default(value.trim());if(!parsed)return null;if(parsed.alpha!==void 0&&parsed.alpha<1)return null;let rgb=toRgb(parsed);if(!rgb)return null;let hsl=toHsl({mode:"rgb",r:clamp01(round4(rgb.r)),g:clamp01(round4(rgb.g)),b:clamp01(round4(rgb.b))});if(!hsl)return null;let h=((hsl.h??0)%360+360)%360,s=clamp01(hsl.s),l=clamp01(hsl.l);if(l<=0.0001||l>=0.9999)s=0;if(s<=0.0001)h=0;return`${Number(h.toFixed(1))} ${pct(s)} ${pct(l)}`}function defaultOf2(value){if(typeof value==="string")return value;return value?.DEFAULT}function fgOf2(value){if(value&&typeof value==="object")return value.foreground;return}function themeVar(name,light,dark){let lightT=light!==void 0?hslTriplet(light):null,darkT=dark!==void 0?hslTriplet(dark):null,triplet=(light===void 0||lightT!==null)&&(dark===void 0||darkT!==null);return{name,...light!==void 0?{light:triplet?lightT:light}:{},...dark!==void 0?{dark:triplet?darkT:dark}:{},triplet}}function collectVars(theme,warnings){let vars=[];for(let{key,pair}of COLOR_SLOTS){let light=defaultOf2(theme.colors[key]),dark=defaultOf2(theme.colorsDark?.[key]);if(light!==void 0||dark!==void 0)vars.push(themeVar(key,light,dark));if(pair){let lightFg=fgOf2(theme.colors[key]),darkFg=fgOf2(theme.colorsDark?.[key]);if(lightFg!==void 0||darkFg!==void 0)vars.push(themeVar(`${key}-foreground`,lightFg,darkFg))}}let paletteNames=new Set([...Object.keys(theme.palette??{}),...Object.keys(theme.paletteDark??{})]);for(let name of[...paletteNames].sort()){if(!isCssIdent(name)||SEMANTIC_SLOTS.has(name))continue;vars.push(themeVar(name,theme.palette?.[name],theme.paletteDark?.[name]))}for(let v of vars)if(!v.triplet)warnings.push(`--${v.name} could not be expressed as an HSL triplet \u2014 mapped as a raw var() in the preset, so opacity modifiers (\`bg-${v.name}/50\`) won't work for it.`);return vars}function paint(v){return v.triplet?`hsl(var(--${v.name}))`:`var(--${v.name})`}function configColor(v){return v.triplet?`hsl(var(--${v.name}) / <alpha-value>)`:`var(--${v.name})`}var TYPESET_CSS_V3_FILENAME="velloo-typeset.css";function radiusBase(theme){let raw=theme.radius.md??theme.radius.lg??theme.radius.sm;if(raw===void 0)return;return typeof raw==="number"?`${raw}px`:sanitizeCssTokenValue(raw)}function emitGlobalsCssV3(theme,vars,opts={}){let lines=[];lines.push("/**"),lines.push(` * Generated by velloo from theme "${sanitizeCssTokenValue(theme.name)}" \u2014 Tailwind v3 variables.`),lines.push(' * Import from the top of your globals.css: @import "./velloo-theme.css";'),lines.push(" * Hand-edits are welcome; re-running `velloo theme
|
|
63
|
+
`}import{existsSync}from"fs";import{mkdir as mkdir2,writeFile as writeFile2}from"fs/promises";import{basename,dirname as dirname2,join as join3}from"path";var toRgb=converter_default("rgb"),toHsl=converter_default("hsl"),clamp01=(n)=>Math.min(1,Math.max(0,n)),round4=(n)=>Math.round(n*1e4)/1e4;function pct(n){return`${Number((clamp01(n)*100).toFixed(1))}%`}function hslTriplet(value){let parsed=parse_default(value.trim());if(!parsed)return null;if(parsed.alpha!==void 0&&parsed.alpha<1)return null;let rgb=toRgb(parsed);if(!rgb)return null;let hsl=toHsl({mode:"rgb",r:clamp01(round4(rgb.r)),g:clamp01(round4(rgb.g)),b:clamp01(round4(rgb.b))});if(!hsl)return null;let h=((hsl.h??0)%360+360)%360,s=clamp01(hsl.s),l=clamp01(hsl.l);if(l<=0.0001||l>=0.9999)s=0;if(s<=0.0001)h=0;return`${Number(h.toFixed(1))} ${pct(s)} ${pct(l)}`}function defaultOf2(value){if(typeof value==="string")return value;return value?.DEFAULT}function fgOf2(value){if(value&&typeof value==="object")return value.foreground;return}function themeVar(name,light,dark){let lightT=light!==void 0?hslTriplet(light):null,darkT=dark!==void 0?hslTriplet(dark):null,triplet=(light===void 0||lightT!==null)&&(dark===void 0||darkT!==null);return{name,...light!==void 0?{light:triplet?lightT:light}:{},...dark!==void 0?{dark:triplet?darkT:dark}:{},triplet}}function collectVars(theme,warnings){let vars=[];for(let{key,pair}of COLOR_SLOTS){let light=defaultOf2(theme.colors[key]),dark=defaultOf2(theme.colorsDark?.[key]);if(light!==void 0||dark!==void 0)vars.push(themeVar(key,light,dark));if(pair){let lightFg=fgOf2(theme.colors[key]),darkFg=fgOf2(theme.colorsDark?.[key]);if(lightFg!==void 0||darkFg!==void 0)vars.push(themeVar(`${key}-foreground`,lightFg,darkFg))}}let paletteNames=new Set([...Object.keys(theme.palette??{}),...Object.keys(theme.paletteDark??{})]);for(let name of[...paletteNames].sort()){if(!isCssIdent(name)||SEMANTIC_SLOTS.has(name))continue;vars.push(themeVar(name,theme.palette?.[name],theme.paletteDark?.[name]))}for(let v of vars)if(!v.triplet)warnings.push(`--${v.name} could not be expressed as an HSL triplet \u2014 mapped as a raw var() in the preset, so opacity modifiers (\`bg-${v.name}/50\`) won't work for it.`);return vars}function paint(v){return v.triplet?`hsl(var(--${v.name}))`:`var(--${v.name})`}function configColor(v){return v.triplet?`hsl(var(--${v.name}) / <alpha-value>)`:`var(--${v.name})`}var TYPESET_CSS_V3_FILENAME="velloo-typeset.css";function radiusBase(theme){let raw=theme.radius.md??theme.radius.lg??theme.radius.sm;if(raw===void 0)return;return typeof raw==="number"?`${raw}px`:sanitizeCssTokenValue(raw)}function emitGlobalsCssV3(theme,vars,opts={}){let lines=[];lines.push("/**"),lines.push(` * Generated by velloo from theme "${sanitizeCssTokenValue(theme.name)}" \u2014 Tailwind v3 variables.`),lines.push(' * Import from the top of your globals.css: @import "./velloo-theme.css";'),lines.push(" * Hand-edits are welcome; re-running `velloo theme export` shows a diff."),lines.push(" */");let googleFonts=theme.typography.googleFonts;if(googleFonts&&googleFonts.length>0){let families=googleFonts.map((f)=>`family=${sanitizeGoogleFontSpec(f)}`).join("&");lines.push(`@import url("https://fonts.googleapis.com/css2?${families}&display=swap");`)}lines.push(""),lines.push("@layer base {"),lines.push(" :root {");let radius=radiusBase(theme);if(radius!==void 0)lines.push(` --radius: ${radius};`);for(let v of vars)if(v.light!==void 0)lines.push(` --${v.name}: ${sanitizeCssTokenValue(v.light)};`);if(lines.push(" }"),vars.some((v)=>v.dark!==void 0)){lines.push(""),lines.push(" .dark {");for(let v of vars)if(v.dark!==void 0)lines.push(` --${v.name}: ${sanitizeCssTokenValue(v.dark)};`);lines.push(" }")}let border=vars.find((v)=>v.name==="border"),background=vars.find((v)=>v.name==="background"),foreground=vars.find((v)=>v.name==="foreground");if(lines.push(""),border)lines.push(" * {"),lines.push(` border-color: ${paint(border)};`),lines.push(" }");if(background||foreground){if(lines.push(" body {"),background)lines.push(` background-color: ${paint(background)};`);if(foreground)lines.push(` color: ${paint(foreground)};`);lines.push(" }")}if(lines.push("}"),lines.push(""),opts.customCss&&opts.customCss.trim()!=="")lines.push("/* === velloo theme/custom.css === */"),lines.push(neutralizeCssText(opts.customCss.trimEnd())),lines.push("");return lines.join(`
|
|
64
64
|
`)}function presetObject(theme,vars){let colors={},byName=new Map(vars.map((v)=>[v.name,v]));for(let{key,pair}of COLOR_SLOTS){let base=byName.get(key);if(!base)continue;let fg=pair?byName.get(`${key}-foreground`):void 0;colors[key]=fg?{DEFAULT:configColor(base),foreground:configColor(fg)}:configColor(base)}for(let v of vars)if(!SEMANTIC_SLOTS.has(v.name)&&!(v.name in colors))colors[v.name]=configColor(v);let extend={colors};if(radiusBase(theme)!==void 0)extend.borderRadius={lg:"var(--radius)",md:"calc(var(--radius) - 2px)",sm:"calc(var(--radius) - 4px)"};let fonts=theme.typography.fontFamily;if(fonts){let fontFamily={};for(let[role,stack]of Object.entries(fonts))if(isCssIdent(role))fontFamily[role]=stack;if(Object.keys(fontFamily).length>0)extend.fontFamily=fontFamily}extend.fontSize=typesetV3FontSize();let spacing={};for(let[name,value]of Object.entries(theme.spacing)){if(!Number.isNaN(Number(name)))continue;if(!isCssIdent(name))continue;if(typeof value==="string"||typeof value==="number")spacing[name]=String(value)}if(Object.keys(spacing).length>0)extend.spacing=spacing;let boxShadow={};for(let[name,value]of Object.entries(theme.shadows??{})){if(!isCssIdent(name))continue;if(typeof value==="string"||typeof value==="number")boxShadow[name]=String(value)}if(Object.keys(boxShadow).length>0)extend.boxShadow=boxShadow;if(theme.keyframes){let keyframes={};for(let[name,steps]of Object.entries(theme.keyframes))if(isCssIdent(name))keyframes[name]=steps;if(Object.keys(keyframes).length>0)extend.keyframes=keyframes}if(theme.animation&&Object.keys(theme.animation).length>0)extend.animation=theme.animation;let preset={darkMode:["class"],safelist:typesetUtilityClasses()},themeSection={},container=theme.container;if(container&&(container.center||container.padding||container.maxWidth))themeSection.container={...container.center?{center:!0}:{},...container.padding?{padding:container.padding}:{},...container.maxWidth?{screens:{"2xl":container.maxWidth}}:{}};return themeSection.extend=extend,preset.theme=themeSection,preset}function emitTailwindPresetV3(theme,vars,moduleKind){let body=JSON.stringify(presetObject(theme,vars),null,2),wiring=moduleKind==="ts"?`import vellooPreset from "./velloo.preset";
|
|
65
65
|
* ...
|
|
66
66
|
* presets: [vellooPreset],`:'presets: [require("./velloo.preset.cjs")],',header=`/**
|
|
@@ -79,10 +79,10 @@ export const darkTheme = ${wrap(options.darkThemeOptions)};
|
|
|
79
79
|
`)}
|
|
80
80
|
|
|
81
81
|
export const theme = ${wrap(themeOptions)};
|
|
82
|
-
${darkBlock}`,diff=await diffFile(themePath,contents),applied=!1;if(options.apply&&!diff.identical)await mkdir4(dirname4(themePath),{recursive:!0}),await writeFile4(themePath,contents,"utf8"),applied=!0;let files=[{path:themePath,contents,diff,applied}],warnings=[];if(options.sourceTheme){let dtcg=await emitDtcgFile(options.sourceTheme,options);files.push(dtcg.file),warnings.push(...dtcg.warnings)}return{files,warnings,notes:[]}}var UTILITY_RENAMES=new Map([["shadow-xs","shadow-sm"],["shadow-sm","shadow"],["drop-shadow-xs","drop-shadow-sm"],["drop-shadow-sm","drop-shadow"],["blur-xs","blur-sm"],["blur-sm","blur"],["backdrop-blur-xs","backdrop-blur-sm"],["backdrop-blur-sm","backdrop-blur"],["outline-hidden","outline-none"]]),ROUNDED_RENAME=/^(rounded(?:-(?:s|e|t|r|b|l|ss|se|es|ee|tl|tr|br|bl))?)-(xs|sm)$/,GRADIENT_RENAME=/^bg-linear-to-(t|tr|r|br|b|bl|l|tl)$/,V4_ONLY_UTILITIES=[[/^inset-shadow(-|$)/,"inset shadows are v4-only \u2014 approximate with shadow-[inset_\u2026]"],[/^inset-ring(-|$)/,"inset rings are v4-only \u2014 approximate with shadow-[inset_\u2026]"],[/^field-sizing-/,"field-sizing is v4-only \u2014 needs custom CSS in v3"],[/^text-shadow(-|$)/,"text shadows are v4-only \u2014 needs custom CSS in v3"],[/^scheme-/,"color-scheme utilities are v4-only \u2014 needs custom CSS in v3"],[/^mask-/,"mask utilities are v4-only \u2014 needs custom CSS in v3"],[/^bg-(radial|conic)(-|$)/,"radial/conic gradient utilities are v4-only \u2014 use an arbitrary background-image in v3"],[/^bg-linear-/,"angle-based bg-linear-* is v4-only \u2014 v3 only has bg-gradient-to-<dir> or an arbitrary background-image"]],V4_ONLY_VARIANTS=[[/^starting$/,"the starting: variant (@starting-style) is v4-only"],[/^inert$/,"the inert: variant is v4-only"],[/^not-/,"the not-* variant is v4-only"],[/^in-/,"the in-* variant is v4-only"],[/^nth-/,"the nth-* variants are v4-only"],[/^\*\*$/,"the **: descendant variant is v4-only"],[/^@/,"container-query variants need the @tailwindcss/container-queries plugin (and a @container parent) in v3"]];function splitVariants(cls){let segments=[],depth=0,start=0;for(let i=0;i<cls.length;i++){let ch=cls[i];if(ch==="["||ch==="(")depth++;else if(ch==="]"||ch===")")depth=Math.max(0,depth-1);else if(ch===":"&&depth===0)segments.push(cls.slice(start,i)),start=i+1}return segments.push(cls.slice(start)),segments}function baseIssue(cls,variants,base){let negative=base.startsWith("-")?"-":"",important=base.endsWith("!"),bare=base.slice(negative.length,important?-1:void 0),withWrappers=(renamed)=>{let v3Base=`${important?"!":""}${negative}${renamed}`;return[...variants,v3Base].join(":")},renamed=UTILITY_RENAMES.get(bare);if(renamed!==void 0){let ringNote=bare==="outline-hidden"?"v3 outline-none keeps a transparent outline for forced-colors mode \u2014 usually the intent":"v4 shifted this size ladder down one step";return{class:cls,v3:withWrappers(renamed),note:ringNote}}if(bare==="ring")return{class:cls,v3:withWrappers("ring-1"),note:"v4 ring is 1px (v3 ring is 3px); v3 also defaults ring color to blue-500 \u2014 pair with an explicit ring-<color>"};if(bare==="ring-3")return{class:cls,v3:withWrappers("ring"),note:"v3 spells the 3px ring as bare `ring`; its default color is blue-500 \u2014 pair with an explicit ring-<color>"};let rounded=ROUNDED_RENAME.exec(bare);if(rounded){let v3Name=rounded[2]==="xs"?`${rounded[1]}-sm`:rounded[1];return{class:cls,v3:withWrappers(v3Name),note:"v4 shifted the rounded ladder down one step"}}let gradient=GRADIENT_RENAME.exec(bare);if(gradient)return{class:cls,v3:withWrappers(`bg-gradient-to-${gradient[1]}`),note:"v3 spells directional gradients bg-gradient-to-<dir>"};for(let[pattern,note]of V4_ONLY_UTILITIES)if(pattern.test(bare))return{class:cls,note};let varShorthand=/^([a-z][a-z0-9-]*)-\((--[a-zA-Z0-9-]+)\)$/.exec(bare);if(varShorthand)return{class:cls,v3:withWrappers(`${varShorthand[1]}-[var(${varShorthand[2]})]`),note:"the (--var) arbitrary-value shorthand is v4-only"};if(important)return{class:cls,v3:withWrappers(bare),note:"v3 puts the important marker first (`!utility`), v4 last (`utility!`)"};return null}function v3ClassIssues(classes){let issues=[],seen=new Set;for(let cls of classes){if(cls===""||seen.has(cls))continue;seen.add(cls);let segments=splitVariants(cls),base=segments[segments.length-1],variants=segments.slice(0,-1),variantHit=variants.flatMap((v)=>V4_ONLY_VARIANTS.filter(([p])=>p.test(v)).map(([,note])=>note)).at(0);if(variantHit!==void 0){issues.push({class:cls,note:variantHit});continue}let issue=baseIssue(cls,variants,base);if(issue)issues.push(issue)}return issues}function classNamesInJsx(jsx){let out=new Set,attr=/className="([^"]*)"/g,m=attr.exec(jsx);while(m!==null){for(let cls of m[1].split(/\s+/))if(cls!=="")out.add(cls);m=attr.exec(jsx)}return[...out]}var HSL_TRIPLET=/^-?[0-9.]+(?:deg)?\s+-?[0-9.]+%\s+-?[0-9.]+%(?:\s*\/\s*[0-9.]+%?)?$/,NON_FAMILY_FONT_ROLE=/^(size|weight|leading|tracking)(-|$)/,PALETTE_KEY=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;function paletteName(rawKey){let name=rawKey.startsWith("color-")?rawKey.slice(6):rawKey;if(SEMANTIC_SLOTS.has(name))return null;return PALETTE_KEY.test(name)?name:null}function isColorish(value){let v=value.trim().toLowerCase();if(HSL_TRIPLET.test(v))return!0;return/^#[0-9a-f]{3,8}$/.test(v)||/^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(/.test(v)||v==="transparent"||v==="currentcolor"}function stripComments(css){let out="",from=0;for(;;){let open=css.indexOf("/*",from);if(open===-1)return out+css.slice(from);let close=css.indexOf("*/",open+2);if(close===-1)return out+css.slice(from);out+=css.slice(from,open),from=close+2}}function matchBrace(css,open){let depth=0;for(let i=open;i<css.length;i++)if(css[i]==="{")depth++;else if(css[i]==="}"&&--depth===0)return i;return-1}function collectDeclarations(body,into){let decl=/--([a-zA-Z0-9-]+)\s*:\s*([^;{}]+);/g,m=decl.exec(body);while(m!==null)into.set(m[1],m[2].trim()),m=decl.exec(body)}function scanBlocks(css,matches,into){let preludeStart=0;for(let i=0;i<css.length;i++){let ch=css[i];if(ch==="{"&&i>preludeStart&&matches(css.slice(preludeStart,i).trim())){let end=matchBrace(css,i);if(end>i)collectDeclarations(css.slice(i+1,end),into)}if(ch==="{"||ch==="}"||ch===";")preludeStart=i+1}}var DARK_SCHEME=/prefers-color-scheme\s*:\s*dark/;function splitDarkMedia(css){let darkVars=new Map,out=css,from=0;for(;;){let at=css.indexOf("@media",from);if(at===-1)break;let start=at;while(start<css.length&&css[start]!=="{"&&css[start]!=="}")start++;if(from=start+1,css[start]!=="{"||!DARK_SCHEME.test(css.slice(at,start)))continue;let end=matchBrace(css,start);if(end>start){let body=css.slice(start+1,end);scanBlocks(body,(p)=>p.startsWith(":root")||p==="*",darkVars),out=out.slice(0,at)+" ".repeat(end+1-at)+out.slice(end+1)}}return{css:out,darkVars}}function resolveVars(value,scopes,depth=0){if(!value.includes("var("))return value;if(depth>4)return null;let unresolved=!1,replaced=value.replace(/var\(\s*--([a-zA-Z0-9-]+)\s*(?:,([^()]*))?\)/g,(_,name,fallback)=>{for(let scope of scopes){let v=scope.get(name);if(v!==void 0)return v}if(fallback!==void 0)return fallback.trim();return unresolved=!0,""});if(unresolved)return null;return resolveVars(replaced.trim(),scopes,depth+1)}function resolveCssVars(value,vars){return resolveVars(value,[vars])}function normalizeColor(value){let v=value.trim();return HSL_TRIPLET.test(v)?`hsl(${v})`:v}function lookupSlot(slot,declScope){return declScope.get(`color-${slot}`)??declScope.get(slot)}function extractColors(declScope,resolveScopes,scopeName,warnings){let colors={};for(let{key,pair}of COLOR_SLOTS){let raw=lookupSlot(key,declScope);if(raw===void 0)continue;let resolved=resolveVars(raw,resolveScopes);if(resolved===null||resolved===""){warnings.push(`${scopeName}: --${key} references an undefined var \u2014 skipped`);continue}let value=normalizeColor(resolved);if(!pair){colors[key]=value;continue}let fgRaw=lookupSlot(`${key}-foreground`,declScope),fg=fgRaw!==void 0?resolveVars(fgRaw,resolveScopes):null;colors[key]=fg!==null&&fg!==""&&fgRaw!==void 0?{DEFAULT:value,foreground:normalizeColor(fg)}:value}return colors}function extractPalette(declScope,resolveScopes){let palette={};for(let[rawKey,raw]of declScope){let name=paletteName(rawKey);if(name===null)continue;if(rawKey.startsWith("color-")||!declScope.has(`color-${rawKey}`)){let resolved=resolveVars(raw,resolveScopes);if(resolved===null||resolved===""||!isColorish(resolved))continue;palette[name]=normalizeColor(resolved)}}return palette}function parseThemeCss(css){let warnings=[],stripped=stripComments(css),{css:lightCss,darkVars:mediaDarkVars}=splitDarkMedia(stripped),rootVars=new Map;scanBlocks(lightCss,(p)=>p.startsWith(":root")||p.startsWith("@theme")||p.startsWith("html"),rootVars);let darkVars=mediaDarkVars;scanBlocks(lightCss,(p)=>/(^|[\s,])\.dark(\s|,|:|$)/.test(p)||p===".dark",darkVars);let colors=extractColors(rootVars,[rootVars],"light",warnings),colorsDark=darkVars.size>0?extractColors(darkVars,[darkVars,rootVars],"dark",warnings):{},palette=extractPalette(rootVars,[rootVars]),paletteDark=darkVars.size>0?extractPalette(darkVars,[darkVars,rootVars]):{},result={colors,colorsDark,palette,paletteDark,warnings,rootVars},radiusRaw=rootVars.get("radius");if(radiusRaw!==void 0){let radius=resolveVars(radiusRaw,[rootVars]);if(radius!==null&&radius!=="")result.radius=radius;else warnings.push("--radius references an undefined var \u2014 skipped")}let fontFamily={};for(let[name,value]of rootVars){let m=/^font-([a-z][a-z0-9-]*)$/.exec(name);if(!m)continue;let role=m[1];if(NON_FAMILY_FONT_ROLE.test(role))continue;let resolved=resolveVars(value,[rootVars]);if(resolved!==null&&resolved!=="")fontFamily[role]=resolved}if(Object.keys(fontFamily).length>0)result.fontFamily=fontFamily;return result}function matchBrace2(src,open){let depth=0;for(let i=open;i<src.length;i++)if(src[i]==="{")depth++;else if(src[i]==="}"&&--depth===0)return i;return-1}function stripComments2(src){return src.replace(/\/\/[^\n]*/g,"").replace(/\/\*[\s\S]*?\*\//g,"")}function objectBody(src,keyRe){let key=src.search(keyRe);if(key===-1)return null;let open=src.indexOf("{",key);if(open===-1)return null;let end=matchBrace2(src,open);if(end===-1)return null;return src.slice(open+1,end)}function parseTailwindContainer(src){let stripped=stripComments2(src),key=stripped.search(/\bcontainer\s*:\s*\{/);if(key===-1)return null;let open=stripped.indexOf("{",key),end=matchBrace2(stripped,open);if(end===-1)return null;let body=stripped.slice(open+1,end),out={},center=/\bcenter\s*:\s*(true|false)/.exec(body);if(center)out.center=center[1]==="true";let padStr=/\bpadding\s*:\s*["'`]([^"'`]+)["'`]/.exec(body);if(padStr?.[1]!==void 0)out.padding=padStr[1];else{let padObj=/\bpadding\s*:\s*\{[^}]*?DEFAULT\s*:\s*["'`]([^"'`]+)["'`]/.exec(body);if(padObj?.[1]!==void 0)out.padding=padObj[1]}let screensBody=/\bscreens\s*:\s*\{([^}]*)\}/.exec(body)?.[1];if(screensBody){let pxs=[...screensBody.matchAll(/:\s*["'`](\d+)px["'`]/g)].map((m)=>Number(m[1])).filter((n)=>!Number.isNaN(n));if(pxs.length>0)out.maxWidth=`${Math.max(...pxs)}px`}return Object.keys(out).length>0?out:null}function paddingClass(p){let rem=/^([\d.]+)rem$/.exec(p),px=/^([\d.]+)px$/.exec(p),units=rem?Number(rem[1])*4:px?Number(px[1])/4:null;return units!==null&&Number.isInteger(units)&&units>=0?`px-${units}`:`px-[${p}]`}function containerClasses(c){let parts=[c.center?"mx-auto":"","w-full"];if(c.padding)parts.push(paddingClass(c.padding));if(c.maxWidth)parts.push(`max-w-[${c.maxWidth}]`);return parts.filter(Boolean).join(" ")}var SAFE_KEY=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;function unquote(raw){let m=/^(["'`])([\s\S]*)\1$/.exec(raw.trim());return m?m[2]:null}function topLevelEntries(body){let entries=[],n=body.length,i=0;while(i<n){while(i<n&&/[\s,;]/.test(body[i]))i++;if(i>=n)break;let key,c0=body[i];if(c0==='"'||c0==="'"||c0==="`"){let j=i+1;while(j<n&&body[j]!==c0)j++;key=body.slice(i+1,j),i=j+1}else{let j=i;while(j<n&&/[A-Za-z0-9_$-]/.test(body[j]))j++;key=body.slice(i,j),i=j}while(i<n&&/\s/.test(body[i]))i++;if(body[i]!==":"||key===""){i=skipValue(body,i);continue}i++;while(i<n&&/\s/.test(body[i]))i++;let start=i;i=skipValue(body,i),entries.push([key,body.slice(start,i).trim()]),i++}return entries}function skipValue(body,from){let n=body.length,i=from,depth=0,str=null;while(i<n){let c=body[i];if(str){if(c==="\\")i++;else if(c===str)str=null}else if(c==='"'||c==="'"||c==="`")str=c;else if(c==="{"||c==="["||c==="(")depth++;else if(c==="}"||c==="]"||c===")")depth--;else if(c===","&&depth===0)break;i++}return i}function stringArray(raw){return[...raw.matchAll(/(["'`])([\s\S]*?)\1/g)].map((m)=>m[2])}function parseStringRecord(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let value=unquote(raw);if(value===null)continue;let k=key.toLowerCase();if(SAFE_KEY.test(k))out[k]=value}return Object.keys(out).length>0?out:void 0}function parseColors(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let name=key.toLowerCase(),value=unquote(raw);if(value!==null){if(SAFE_KEY.test(name))out[name]=value;continue}if(raw.startsWith("{")){let inner=objectBody(raw,/^\s*\{/);if(!inner)continue;for(let[sub,subRaw]of topLevelEntries(inner)){let subVal=unquote(subRaw),flat=`${name}-${sub.toLowerCase()}`;if(subVal!==null&&SAFE_KEY.test(flat))out[flat]=subVal}}}return Object.keys(out).length>0?out:void 0}function parseFontFamily(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let k=key.toLowerCase();if(!SAFE_KEY.test(k))continue;let single=unquote(raw);if(single!==null)out[k]=single;else if(raw.startsWith("[")){let stack=stringArray(raw).join(", ");if(stack)out[k]=stack}}return Object.keys(out).length>0?out:void 0}var SAFE_STEP=/^(from|to|[\d.%,\s]+)$/i,SAFE_PROP=/^-{0,2}[a-z][a-z0-9-]*$/,SAFE_IDENT=/^[A-Za-z][A-Za-z0-9_-]*$/;function kebab(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}function parseKeyframes(body){if(body===null)return;let out={};for(let[name,rawObj]of topLevelEntries(body)){if(!SAFE_IDENT.test(name)||!rawObj.startsWith("{"))continue;let stepsBody=objectBody(rawObj,/^\s*\{/);if(!stepsBody)continue;let steps={};for(let[step,rawDecls]of topLevelEntries(stepsBody)){if(!SAFE_STEP.test(step.trim())||!rawDecls.startsWith("{"))continue;let declsBody=objectBody(rawDecls,/^\s*\{/);if(!declsBody)continue;let decls={};for(let[prop,rawVal]of topLevelEntries(declsBody)){let cssProp=kebab(prop),value=unquote(rawVal);if(value===null||/[{};]/.test(value)||!SAFE_PROP.test(cssProp))continue;decls[cssProp]=value}if(Object.keys(decls).length>0)steps[step.trim()]=decls}if(Object.keys(steps).length>0)out[name]=steps}return Object.keys(out).length>0?out:void 0}function parseThemeExtend(src){let extend=objectBody(stripComments2(src),/\bextend\s*:\s*\{/);if(extend===null)return null;let out={},colors=parseColors(objectBody(extend,/\bcolors\s*:\s*\{/));if(colors)out.colors=colors;let spacing=parseStringRecord(objectBody(extend,/\bspacing\s*:\s*\{/));if(spacing)out.spacing=spacing;let boxShadow=parseStringRecord(objectBody(extend,/\bboxShadow\s*:\s*\{/));if(boxShadow)out.boxShadow=boxShadow;let fontFamily=parseFontFamily(objectBody(extend,/\bfontFamily\s*:\s*\{/));if(fontFamily)out.fontFamily=fontFamily;let keyframes=parseKeyframes(objectBody(extend,/\bkeyframes\s*:\s*\{/));if(keyframes)out.keyframes=keyframes;let animation=parseStringRecord(objectBody(extend,/\banimation\s*:\s*\{/));if(animation)out.animation=animation;return Object.keys(out).length>0?out:null}import{mkdir as mkdir5,rename,writeFile as writeFile5}from"fs/promises";import{dirname as dirname5,join as join6}from"path";async function writeJsonAtomic(path,value){await mkdir5(dirname5(path),{recursive:!0});let tmp=`${path}.${process.pid}.${Date.now()}.tmp`,body=`${JSON.stringify(value,null,2)}
|
|
83
|
-
`;await writeFile5(tmp,body,"utf8"),await rename(tmp,path)}async function writeText(path,body){await mkdir5(dirname5(path),{recursive:!0}),await writeFile5(path,body,"utf8")}function pngSize2(bytes){if(bytes.length<24||bytes.toString("latin1",12,16)!=="IHDR")return null;let width=bytes.readUInt32BE(16),height=bytes.readUInt32BE(20);return width>0&&height>0?{width,height}:null}var ALLOWED_ASSET_EXTENSIONS=new Set([".png",".jpg",".jpeg",".gif",".webp",".avif",".svg",".ico",".bmp",".woff",".woff2",".ttf",".otf"]),ASSET_MIME={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".bmp":"image/bmp",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf"};function extOf(filename){let dot=filename.lastIndexOf(".");return dot===-1?"":filename.slice(dot).toLowerCase()}function isAllowedAssetExt(filename){return ALLOWED_ASSET_EXTENSIONS.has(extOf(filename))}function sanitizeFilename(filename){return(filename.split(/[/\\]/).pop()??"asset").replace(/\.\./g,"_").replace(/[^a-zA-Z0-9._-]/g,"_").replace(/^[._-]+/,"").replace(/_+/g,"_")||"asset"}async function storeAsset(root,filename,bytes){let safe=sanitizeFilename(filename);if(!isAllowedAssetExt(safe))throw Error(`storeAsset: ${safe} is not an allowed image/font asset type`);let dir=join6(root,"assets");await mkdir5(dir,{recursive:!0});let out=/\.svg$/i.test(safe)?Buffer.from(sanitizeSvgMarkup(bytes.toString("utf8")),"utf8"):bytes;return await writeFile5(join6(dir,safe),out),{assetPath:`assets/${safe}`,url:`/assets/${safe}`,bytes:out.length}}var TAILWIND_CLASSNAME={kind:"tailwind-classname",prop:"className",needsTailwindJit:!0,editorLabel:"Tailwind classes"},SX_PROP={kind:"sx",prop:"sx",needsTailwindJit:!1,editorLabel:"sx props"},STYLE_PROP={kind:"style",prop:"style",needsTailwindJit:!1,editorLabel:"Inline styles"},STYLE_CHANNELS={"tailwind-classname":TAILWIND_CLASSNAME,sx:SX_PROP,style:STYLE_PROP},CSS_FRAMEWORK_CHANNEL={tailwind:"tailwind-classname",none:"style"};function catalogFromManifest(manifest,opts){return manifest.map((descriptor)=>({id:descriptor.id,installed:opts.installed??!0,importPath:opts.importPath}))}function identifierRef(path){if(!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(path))throw Error(`identifierRef: "${path}" is not a bare identifier path`);return{$identifier:path}}function styleChannelOf(p,folderCss){let adapter=p,allowed=adapter.styleChannels??[adapter.styleChannel?.kind??"tailwind-classname"],fallback=allowed[0]??"tailwind-classname";if(folderCss){let wanted=CSS_FRAMEWORK_CHANNEL[folderCss];if(allowed.includes(wanted))return STYLE_CHANNELS[wanted]}return STYLE_CHANNELS[fallback]}class UnknownProviderError extends Error{id;constructor(id,message){super(message??`Unknown component provider: "${id}"`);this.name="UnknownProviderError",this.id=id}}function createProviderLoader(factories){return async(library)=>{let factory=factories[library.id];if(!factory){let known=Object.keys(factories).sort();throw new UnknownProviderError(library.id,`Unknown component provider: "${library.id}". Known providers: ${known.join(", ")}.`)}return factory(library)}}var COMPONENT_GROUPS=[{id:"actions",label:"Actions"},{id:"forms",label:"Forms & Inputs"},{id:"display",label:"Display"},{id:"feedback",label:"Feedback"},{id:"navigation",label:"Navigation"},{id:"overlays",label:"Overlays"},{id:"layout",label:"Layout"},{id:"typography",label:"Typography"},{id:"visuals",label:"Visuals"},{id:"chat",label:"Chat & AI"}],UNGROUPED_LABEL="Components";import{existsSync as existsSync2,lstatSync,readdirSync,readFileSync as readFileSync2,realpathSync}from"fs";import{rm}from"fs/promises";import{homedir}from"os";import{basename as basename2,dirname as dirname6,join as join7,relative,resolve}from"path";var ID=/^[A-Za-z0-9_-]{8,64}$/,PROJECT_NAME=/^[a-z0-9][a-z0-9._-]*$/i,RecordSchema=object({root:string().min(1),appRoot:string().min(1),projectName:string().regex(PROJECT_NAME),designPath:string().min(1).optional()});function storageRoot(){return resolve(process.env.VELLOO_DESIGNS_HOME??join7(homedir(),".velloo","designs"))}function locationsDir(){return join7(storageRoot(),".locations")}function realOr(path){return existsSync2(path)?realpathSync(path):resolve(path)}function within(parent,child){let rel=relative(realOr(parent),realOr(child));return rel===""||!rel.startsWith("..")&&!/^(?:[\\/]|[A-Za-z]:)/.test(rel)}function managedDesignPath(id){if(!ID.test(id))throw Error(`Invalid managed design identifier: ${JSON.stringify(id)}`);let folder=join7(storageRoot(),id);if(lstatSync(folder,{throwIfNoEntry:!1})?.isSymbolicLink())throw Error(`Managed design ${folder} is a symbolic link. Restore the actual folder here before opening it.`);return folder}function managedDesignId(folder){let abs=resolve(folder);if(realOr(dirname6(abs))!==realOr(storageRoot()))return null;let id=basename2(abs);return ID.test(id)?id:null}function listLocalDesigns(){let names;try{names=readdirSync(locationsDir()).filter((name)=>name.endsWith(".json"))}catch{return[]}let out=[];for(let name of names.sort()){let id=name.slice(0,-5);if(!ID.test(id))continue;try{let record=RecordSchema.parse(JSON.parse(readFileSync2(join7(locationsDir(),name),"utf8")));out.push({id,root:record.root,appRoot:record.appRoot,projectName:record.projectName,designRoot:record.designPath??managedDesignPath(id)})}catch{}}return out}function localDesignsAt(dir){return listLocalDesigns().filter((design)=>within(design.root,dir))}function localDesignOf(folder){let target=realOr(folder);return listLocalDesigns().find((design)=>realOr(design.designRoot)===target)??null}async function writeLocalDesign(id,record){if(!ID.test(id))throw Error(`Invalid local design identifier: ${JSON.stringify(id)}`);try{await writeJsonAtomic(join7(locationsDir(),`${id}.json`),RecordSchema.parse({...record,root:resolve(record.root),appRoot:resolve(record.appRoot),...record.designPath?{designPath:resolve(record.designPath)}:{}}))}catch(error){throw Error(`Could not save the local design record ${id}. Check write permissions and free space in ${storageRoot()}, then retry. Design content was retained. ${error instanceof Error?error.message:error}`)}}async function removeLocalDesign(id){if(!ID.test(id))return;await rm(join7(locationsDir(),`${id}.json`),{force:!0})}function resolveProjectPath(folder,path){if(!path.startsWith("project:"))return resolve(folder,path);let design=localDesignOf(folder);if(!design)throw Error(`Project-relative reference ${path} has no application on this machine for ${folder}. Run \`velloo folder bind ${folder}\` from the application checkout.`);let rel=path.slice(8);if(/^(?:[\\/]|[A-Za-z]:)/.test(rel))throw Error(`Invalid project-relative path: ${path}`);return resolve(design.appRoot,rel.replace(/\\/g,"/"))}import{readFile as readFile2}from"fs/promises";import{dirname as dirname7,join as join8,resolve as resolve2}from"path";async function findRepoManifest(startDir){if(localDesignOf(startDir))return null;let dir=resolve2(startDir);for(;;){let path=join8(dir,REPO_MANIFEST_FILE);try{let parsed=RepoManifestSchema.safeParse(JSON.parse(await readFile2(path,"utf8")));if(parsed.success)return{path,dir,manifest:parsed.data};return null}catch(err){if(err.code!=="ENOENT")return null}let parent=dirname7(dir);if(parent===dir)return null;dir=parent}}async function readRepoFeedback(startDir){let feedback=(await findRepoManifest(startDir))?.manifest.feedback;return feedback?{enabled:feedback.enabled}:null}async function writeRepoFeedback(startDir,feedback){let found=await findRepoManifest(startDir);if(!found)return null;return await writeJsonAtomic(found.path,{...found.manifest,feedback:{enabled:feedback.enabled}}),found.path}import{mkdir as mkdir6,readFile as readFile3,writeFile as writeFile6}from"fs/promises";import{homedir as homedir2}from"os";import{dirname as dirname8,join as join9}from"path";var prefsPath=()=>process.env.VELLOO_PREFS_PATH??join9(homedir2(),".velloo","prefs.json");async function readAll(){try{let raw=JSON.parse(await readFile3(prefsPath(),"utf8"));if(raw.version===1)return raw}catch{}return{version:1}}async function readFeedbackContactOk(){return(await readAll()).feedbackContactOk===!0}async function writeFeedbackContactOk(contactOk){let next={...await readAll(),version:1,feedbackContactOk:contactOk},path=prefsPath();await mkdir6(dirname8(path),{recursive:!0}),await writeFile6(path,`${JSON.stringify(next,null,2)}
|
|
84
|
-
`,"utf8")}import{readdir,readFile as readFile4}from"fs/promises";import{basename as basename3,extname,join as join10,resolve as resolve3}from"path";function keyOf(e){if(e.kind==="screen")return`screen:${e.screenId}${suffix(e.coalesceKey)}`;if(e.kind==="board")return`board:${e.boardId}`;if(e.kind==="boards")return null;if(e.kind==="snippet")return`snippet:${e.snippetId}${suffix(e.coalesceKey)}`;if(!e.coalesceKey)return null;return`theme:${e.themeName}:${e.coalesceKey}`}function suffix(coalesceKey){return coalesceKey?`:${coalesceKey}`:""}class HistoryManager{undoStack=[];redoStack=[];push(entry){let stamped={...entry,ts:Date.now()},top=this.undoStack[this.undoStack.length-1],key=keyOf(stamped),named="coalesceKey"in entry&&entry.coalesceKey!==void 0,inWindow=top?.ts!==void 0&&stamped.ts!==void 0&&stamped.ts-top.ts<800;if(key!==null&&top!==void 0&&(named||inWindow)){if(keyOf(top)===key){top.ts=stamped.ts,this.redoStack.length=0;return}}if(this.undoStack.push(stamped),this.undoStack.length>50)this.undoStack.splice(0,this.undoStack.length-50);this.redoStack.length=0}pushRedo(entry){if(this.redoStack.push(entry),this.redoStack.length>50)this.redoStack.splice(0,this.redoStack.length-50)}pushUndoSilent(entry){if(this.undoStack.push(entry),this.undoStack.length>50)this.undoStack.splice(0,this.undoStack.length-50)}popUndo(){return this.undoStack.pop()}popRedo(){return this.redoStack.pop()}depths(){return{undo:this.undoStack.length,redo:this.redoStack.length}}truncateUndoTo(undoDepth){while(this.undoStack.length>undoDepth)this.undoStack.pop()}}function screenIdFromFilename(filename){return basename3(filename,extname(filename))}function boardIdFromFilename(filename){return basename3(filename,extname(filename))}function snippetIdFromFilename(filename){return basename3(filename,extname(filename))}async function readJson(path){let raw=await readFile4(path,"utf8");return JSON.parse(raw)}async function readJsonOrNull(path){try{return await readJson(path)}catch(err){if(err.code==="ENOENT")return null;throw err}}async function loadDir(dir,parse,idFromFile,predicate){let out=new Map,entries;try{entries=await readdir(dir)}catch(err){if(err.code==="ENOENT")return out;throw err}entries.sort((a,b)=>a.localeCompare(b));for(let file of entries){if(extname(file)!==".json")continue;if(predicate&&!predicate(file))continue;let raw=await readJson(join10(dir,file));out.set(idFromFile(file),parse(raw))}return out}async function loadAnnotations(root,screenIds){let annotations=new Map;for(let screenId of screenIds){let raw=await readJsonOrNull(join10(root,"screens",`${screenId}.annotations.json`));annotations.set(screenId,raw===null?[]:raw.map((r)=>AnnotationSchema.parse(r)))}return annotations}async function loadBoardNotes(root,boardIds){let notes=new Map;for(let boardId of boardIds){let raw=await readJsonOrNull(join10(root,"boards",`${boardId}.notes.json`));notes.set(boardId,raw===null?[]:raw.map((r)=>CanvasNoteSchema.parse(r)))}return notes}function isPlainJson(file){return!basename3(file,".json").includes(".")}async function loadDesignFolder(folder,options={}){let root=resolve3(folder),configRaw=await readJson(join10(root,".design","config.json")),themeRaw=await readJson(join10(root,"theme","default.json")),version=schemaVersionOf(configRaw);if(version>CURRENT_SCHEMA_VERSION)throw Error(`velloo: ${root} uses design-folder schema version ${version}, but this velloo only knows version ${CURRENT_SCHEMA_VERSION}. Upgrade velloo to open it.`);if(version<CURRENT_SCHEMA_VERSION)throw Error(`velloo: ${root} uses design-folder schema version ${version} (current: ${CURRENT_SCHEMA_VERSION}). Run \`velloo upgrade ${root}\` to migrate it.`);let parsedConfig=ConfigSchema.parse(configRaw),feedback=(options.preferences===!1?null:await readRepoFeedback(root))??parsedConfig.feedback,config=feedback?{...parsedConfig,feedback:{...feedback,contactOk:await readFeedbackContactOk()}}:parsedConfig,theme=ThemeSchema.parse(themeRaw),screens=await loadDir(join10(root,"screens"),(raw)=>ScreenSchema.parse(raw),screenIdFromFilename,isPlainJson),snippets=await loadDir(join10(root,"snippets"),(raw)=>SnippetSchema.parse(raw),snippetIdFromFilename),boards=await loadDir(join10(root,"boards"),(raw)=>BoardSchema.parse(raw),boardIdFromFilename,isPlainJson),annotations=await loadAnnotations(root,screens.keys()),notes=await loadBoardNotes(root,boards.keys()),customCss=await readCustomCss(root),themes=await loadDir(join10(root,"theme"),(raw)=>ThemeSchema.parse(raw),snippetIdFromFilename);return themes.set("default",theme),{root,config,theme,history:new HistoryManager,customCss,themes,screens,boards,snippets,annotations,notes}}function orderedBoards(folder){let order=folder.config.boardOrder;if(!order||order.length===0)return[...folder.boards.entries()];let seen=new Set,out=[];for(let id of order){let board=folder.boards.get(id);if(board&&!seen.has(id))out.push([id,board]),seen.add(id)}for(let[id,board]of folder.boards)if(!seen.has(id))out.push([id,board]);return out}function activeBoards(folder){return orderedBoards(folder).filter(([,board])=>!isArchived(board))}async function reloadScreen(folder,screenId){let path=join10(folder.root,"screens",`${screenId}.json`);try{let raw=await readJson(path),screen=ScreenSchema.parse(raw);return folder.screens.set(screenId,screen),screen}catch(err){if(err.code==="ENOENT")return folder.screens.delete(screenId),folder.annotations.delete(screenId),null;throw err}}async function reloadBoard(folder,boardId){let path=join10(folder.root,"boards",`${boardId}.json`);try{let raw=await readJson(path),board=BoardSchema.parse(raw);return folder.boards.set(boardId,board),board}catch(err){if(err.code==="ENOENT")return folder.boards.delete(boardId),folder.notes.delete(boardId),null;throw err}}async function reloadTheme(folder){let raw=await readJson(join10(folder.root,"theme","default.json")),theme=ThemeSchema.parse(raw);return folder.theme=theme,folder.customCss=await readCustomCss(folder.root),folder.themes=await loadDir(join10(folder.root,"theme"),(r)=>ThemeSchema.parse(r),snippetIdFromFilename),folder.themes.set("default",theme),theme}function themeByName(folder,name){if(!name)return folder.theme;return folder.themes.get(name)??folder.theme}function resolveNamedTheme(folder,name){if(!name)return{ok:!0,theme:folder.theme};let theme=folder.themes.get(name);if(theme)return{ok:!0,theme};let known=[...folder.themes.keys()].join(", ")||"(none)";return{ok:!1,message:`Unknown theme "${name}". Known themes: ${known}.`}}function pinnedThemeForScreen(folder,screenId){let boardsByTheme=new Map;for(let board of folder.boards.values()){if(isArchived(board))continue;if(!board.frames.some((f)=>f.screen===screenId))continue;let name=board.theme??"default",hosts=boardsByTheme.get(name)??[];hosts.push(board.id),boardsByTheme.set(name,hosts)}if(boardsByTheme.size===0)return{ok:!0,name:void 0};if(boardsByTheme.size===1){let[only]=boardsByTheme.keys();return{ok:!0,name:only==="default"?void 0:only}}let list=[...boardsByTheme.entries()].map(([name,hosts])=>`"${name}" (${hosts.join(", ")})`).join(", ");return{ok:!1,message:`Screen "${screenId}" is hosted by boards with different themes: ${list}. Pass theme: to pick the one to render.`}}function pinnedSchemeForScreen(folder,screenId){let hostsByScheme=new Map;for(let board of folder.boards.values()){if(isArchived(board))continue;for(let frame of board.frames){if(frame.screen!==screenId)continue;let scheme=frame.scheme??"canvas default",hosts=hostsByScheme.get(scheme)??[];hosts.push(`${board.id}/${frame.id}`),hostsByScheme.set(scheme,hosts)}}if(hostsByScheme.size===0)return{ok:!0,scheme:void 0};if(hostsByScheme.size===1){let[only]=hostsByScheme.keys();return{ok:!0,scheme:only==="canvas default"?void 0:only}}let list=[...hostsByScheme.entries()].map(([scheme,hosts])=>`"${scheme}" (${hosts.join(", ")})`).join(", ");return{ok:!1,message:`Screen "${screenId}" is hosted by frames with different color schemes: ${list}. Pass mode: "light" or "dark" to pick the one to render.`}}async function readCustomCss(root){try{return await readFile4(join10(root,"theme","custom.css"),"utf8")}catch(err){if(err.code==="ENOENT")return"";throw err}}async function reloadSnippet(folder,snippetId){let path=join10(folder.root,"snippets",`${snippetId}.json`);try{let raw=await readJson(path),snippet=SnippetSchema.parse(raw);return folder.snippets.set(snippetId,snippet),snippet}catch(err){if(err.code==="ENOENT")return folder.snippets.delete(snippetId),null;throw err}}async function reloadAnnotations(folder,screenId){let raw=await readJsonOrNull(join10(folder.root,"screens",`${screenId}.annotations.json`)),parsed=raw===null?[]:raw.map((r)=>AnnotationSchema.parse(r));return folder.annotations.set(screenId,parsed),parsed}async function reloadNotes(folder,boardId){let raw=await readJsonOrNull(join10(folder.root,"boards",`${boardId}.notes.json`)),parsed=raw===null?[]:raw.map((r)=>CanvasNoteSchema.parse(r));return folder.notes.set(boardId,parsed),parsed}var import_react3=__toESM(require_react_development(),1);var React4=__toESM(require_react_development(),1),jsx_dev_runtime10=__toESM(require_react_jsx_dev_runtime_development(),1),MAX_LABEL_CHARS=80;function summarizeValue(value){if(value===null)return"null";if(value===void 0)return"\u2014";if(typeof value==="string")return value.length>MAX_LABEL_CHARS?`"${value.slice(0,MAX_LABEL_CHARS-1)}\u2026"`:`"${value}"`;if(typeof value==="number"||typeof value==="boolean")return String(value);if(Array.isArray(value))return`[${value.length}]`;if(typeof value==="object")return"{}";return String(value)}function pickShownProps(declared,resolved){let out=[];for(let prop of declared){if(prop.name in resolved)out.push({name:prop.name,value:summarizeValue(resolved[prop.name])});else if(prop.defaultValue!==void 0)out.push({name:prop.name,value:prop.defaultValue});if(out.length>=6)break}return out}function ExtensionPlaceholder({id,extension,resolvedProps,className,...rest}){let shown=pickShownProps(extension.props,resolvedProps),trimmedClassName=["flex flex-col gap-1.5 rounded-md border border-dashed border-border bg-muted/40 px-4 py-3 text-card-foreground",className].filter(Boolean).join(" ");return jsx_dev_runtime10.jsxDEV("div",{"data-velloo-extension":id,"data-velloo-extension-path":extension.importPath,className:trimmedClassName,...rest,children:[jsx_dev_runtime10.jsxDEV("div",{className:"flex items-baseline justify-between gap-2",children:[jsx_dev_runtime10.jsxDEV("span",{className:"font-mono text-xs font-semibold text-foreground",children:id},void 0,!1,void 0,this),jsx_dev_runtime10.jsxDEV("span",{className:"font-mono text-[10px] uppercase tracking-wider text-muted-foreground",children:"extension"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),extension.description?jsx_dev_runtime10.jsxDEV("div",{className:"text-xs text-muted-foreground",children:extension.description},void 0,!1,void 0,this):null,shown.length>0?jsx_dev_runtime10.jsxDEV("div",{className:"mt-1 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-0.5 font-mono text-[11px] leading-tight",children:shown.map((row)=>jsx_dev_runtime10.jsxDEV(React4.Fragment,{children:[jsx_dev_runtime10.jsxDEV("span",{className:"text-muted-foreground",children:row.name},void 0,!1,void 0,this),jsx_dev_runtime10.jsxDEV("span",{className:"truncate",children:row.value},void 0,!1,void 0,this)]},row.name,!0,void 0,this))},void 0,!1,void 0,this):null,jsx_dev_runtime10.jsxDEV("div",{className:"mt-1 font-mono text-[10px] text-muted-foreground/70",children:extension.importPath},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var jsx_dev_runtime11=__toESM(require_react_jsx_dev_runtime_development(),1),NON_PROP_KEYS=new Set(["data-node-path","children","className"]);function serializeProps(resolved){let out={};for(let[key,value]of Object.entries(resolved)){if(NON_PROP_KEYS.has(key))continue;out[key]=value}try{return JSON.stringify(out)}catch{return"{}"}}function LiveIslandMarker({id,extension,resolvedProps,className,...rest}){let isContentFit=extension.fit==="content",wrapperClassName=[isContentFit?"relative w-full":"relative aspect-video w-full",className].filter(Boolean).join(" ");return jsx_dev_runtime11.jsxDEV("div",{"data-live-node":"true","data-live-ref":id,"data-live-props":serializeProps(resolvedProps),"data-live-fit":isContentFit?"content":void 0,className:wrapperClassName,...rest,children:jsx_dev_runtime11.jsxDEV(ExtensionPlaceholder,{id,extension,resolvedProps,className},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function providerForScreen(screen,providers,defaultProvider){if(!screen.library)return defaultProvider;return providers[screen.library]??defaultProvider}function buildExtensionRegistry(extensions){let out={};for(let[id,extension]of Object.entries(extensions)){let Component=extension.render==="live"?LiveIslandMarker:ExtensionPlaceholder;out[id]=(resolvedProps)=>import_react3.createElement(Component,{id,extension,resolvedProps,"data-node-path":resolvedProps["data-node-path"],className:typeof resolvedProps.className==="string"?resolvedProps.className:void 0})}return out}function registryForScreen(screen,providers,defaultProvider,extensions,folderCss){let provider=providerForScreen(screen,providers,defaultProvider),channel=styleChannelOf(provider,folderCss),base=provider.registryForChannel?.(channel.kind)??provider.registry;if(Object.keys(extensions).length===0)return base;return{...base,...buildExtensionRegistry(extensions)}}function renderPassForScreen(screen,providers,defaultProvider,theme,dark=!1){return providerForScreen(screen,providers,defaultProvider).renderPass?.(theme,dark)}import{readFile as readFile5}from"fs/promises";import{extname as extname2,join as join11,normalize}from"path";var STANDALONE_WARN_BYTES=8388608,ASSET_REF=/\/assets\/[A-Za-z0-9._@\-/]+/g,MIME={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf",".otf":"font/otf",".mp4":"video/mp4",".webm":"video/webm"},FONT_FETCH_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";async function inlineStandaloneDocument(html,opts){let warnings=[],out=await inlineAssets(html,opts.assetRoot,warnings);if(out=await inlineGoogleFonts(out,warnings),!opts.skipSizeWarning)warnings.push(...sizeWarning(out));return{html:out,warnings}}function sizeWarning(html){let bytes=Buffer.byteLength(html,"utf8");return bytes>STANDALONE_WARN_BYTES?[`the exported file is ${(bytes/1048576).toFixed(1)}MB \u2014 embedded assets add up; consider trimming large images`]:[]}async function inlineAssets(html,assetRoot,warnings){let refs=[...new Set(html.match(ASSET_REF)??[])],out=html;for(let ref of refs){let rel=normalize(ref.replace(/^\//,""));if(rel.startsWith(".."))continue;try{let bytes=await readFile5(join11(assetRoot,rel)),mime=MIME[extname2(rel).toLowerCase()]??"application/octet-stream";out=out.replaceAll(ref,`data:${mime};base64,${bytes.toString("base64")}`)}catch{warnings.push(`asset not found, left as a dead reference: ${ref}`)}}return out}async function inlineGoogleFonts(html,warnings){let linkMatch=/<link rel="stylesheet" href="(https:\/\/fonts\.googleapis\.com\/css2[^"]*)"\s*\/>/.exec(html);if(!linkMatch?.[1])return html;let cssUrl=linkMatch[1].replaceAll("&","&");try{let cssRes=await fetch(cssUrl,{headers:{"user-agent":FONT_FETCH_UA}});if(!cssRes.ok)throw Error(`fonts css fetch failed (${cssRes.status})`);let css=await cssRes.text(),fontUrls=[...new Set([...css.matchAll(/url\((https:\/\/fonts\.gstatic\.com[^)]+)\)/g)].map((m)=>m[1]))];for(let url of fontUrls){let res=await fetch(url);if(!res.ok)throw Error(`font file fetch failed (${res.status})`);let bytes=Buffer.from(await res.arrayBuffer()),mime=url.endsWith(".woff2")?"font/woff2":MIME[extname2(new URL(url).pathname)]??"font/woff2";css=css.replaceAll(url,`data:${mime};base64,${bytes.toString("base64")}`)}return html.replace(/\n[ \t]*<link rel="preconnect" href="https:\/\/fonts\.[^"]*"(?: crossorigin)? ?\/>/g,"").replace(linkMatch[0],`<style data-velloo-fonts>${css}</style>`)}catch(err){return warnings.push(`webfonts were not embedded (${err instanceof Error?err.message:String(err)}) \u2014 the file loads them from Google Fonts when opened online`),html}}function findFrame(folder,frameId){for(let board of folder.boards.values()){let frame=board.frames.find((f)=>f.id===frameId);if(frame)return{board,frame}}return null}function exportFilename(name,ext){let dashed=name.replace(/[^\w.-]+/g,"-"),start=0,end=dashed.length;while(start<end&&dashed[start]==="-")start++;while(end>start&&dashed[end-1]==="-")end--;return`${dashed.slice(start,end)||"export"}.${ext}`}async function renderExportHtml(p,screen,opts){let theme=themeByName(p.folder,opts.themeName),config=p.folder.config,canvasBundle=opts.standalone?void 0:await p.canvasBundleFor?.(screen,theme,opts.dark),baseHref=opts.standalone?void 0:p.assetOrigin?.(),liveBundleUrl=opts.standalone?void 0:p.liveBundleUrl?.(),{html}=await renderScreen(screen,theme,{viewport:opts.viewport,snapshotCss:await p.snapshotCss(),registry:registryForScreen(screen,p.providers,p.defaultProvider,config.extensions??{},config.styling?.framework),renderPass:renderPassForScreen(screen,p.providers,p.defaultProvider,theme,opts.dark),snippets:p.folder.snippets,customCss:p.folder.customCss,dark:opts.dark,...baseHref?{baseHref}:{},...liveBundleUrl?{liveBundleUrl}:{},...canvasBundle?{canvasBundle}:{},...opts.standalone?{includeRuntime:!1}:{}});return html}var themeOf=(board,override)=>override??board.theme;function screenOf(p,frame){let screen=p.folder.screens.get(frame.screen);if(!screen)throw Error(`frame "${frame.id}" points at missing screen "${frame.screen}"`);return screen}async function exportFramePng(p,board,frame,opts={}){let screen=screenOf(p,frame),viewport={w:frame.w,h:frame.h},themeName=themeOf(board,opts.theme),scale=opts.scale??1;if(opts.mode==="compare"){let[light,dark]=await Promise.all([renderExportHtml(p,screen,{dark:!1,themeName,viewport}),renderExportHtml(p,screen,{dark:!0,themeName,viewport})]);return screenshotCompareBuffer({leftHtml:light,rightHtml:dark,viewport,deviceScaleFactor:scale})}let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName,viewport}),{png}=await captureScreenshot({html,viewport,fullPage:!1,deviceScaleFactor:scale});return png}async function exportBoardPng(p,board,opts={}){let composite=await boardComposite(p,board,opts,!1),{png}=await captureScreenshot({html:composite.html,viewport:composite.viewport,fullPage:!1,deviceScaleFactor:opts.scale??1});return png}async function exportFramePdf(p,board,frame,opts={}){let screen=screenOf(p,frame),viewport={w:frame.w,h:frame.h},html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:themeOf(board,opts.theme),viewport});return pdfPageBuffer({html,viewport})}async function exportBoardPdf(p,board,opts={}){let dark=opts.mode==="dark",themeName=themeOf(board,opts.theme),pages=[];for(let frame of board.frames){let screen=p.folder.screens.get(frame.screen);if(!screen)continue;let viewport={w:frame.w,h:frame.h};pages.push({html:await renderExportHtml(p,screen,{dark,themeName,viewport}),viewport,label:frame.label??screen.name??screen.id})}if(pages.length===0)throw Error(`board "${board.id}" has no exportable frames`);return pdfDeckBuffer(pages)}async function exportScreenHtml(p,screen,opts){let html=await renderExportHtml(p,screen,{dark:opts.dark??!1,themeName:opts.theme,viewport:opts.viewport,standalone:!0});return inlineStandaloneDocument(html,{assetRoot:p.folder.root})}async function exportScreenPng(p,screen,opts){let{viewport}=opts;if(opts.mode==="compare"){let[light,dark]=await Promise.all([renderExportHtml(p,screen,{dark:!1,themeName:opts.theme,viewport}),renderExportHtml(p,screen,{dark:!0,themeName:opts.theme,viewport})]);return screenshotCompareBuffer({leftHtml:light,rightHtml:dark,viewport,deviceScaleFactor:opts.scale??1})}let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:opts.theme,viewport}),{png}=await captureScreenshot({html,viewport,fullPage:!0,deviceScaleFactor:opts.scale??1});return png}async function exportScreenPdf(p,screen,opts){let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:opts.theme,viewport:opts.viewport});return pdfPageBuffer({html,viewport:opts.viewport})}async function exportFrameHtml(p,board,frame,opts={}){let screen=screenOf(p,frame);return exportScreenHtml(p,screen,{dark:opts.mode==="dark",...themeOf(board,opts.theme)?{theme:themeOf(board,opts.theme)}:{},viewport:{w:frame.w,h:frame.h}})}async function exportBoardHtml(p,board,opts={}){let composite=await boardComposite(p,board,opts,!0);return{html:composite.html,warnings:[...composite.warnings,...sizeWarning(composite.html)]}}async function boardComposite(p,board,opts,standalone){let dark=opts.mode==="dark",themeName=themeOf(board,opts.theme),warnings=[],frames=[];for(let frame of board.frames){let screen=p.folder.screens.get(frame.screen);if(!screen)continue;let html=await renderExportHtml(p,screen,{dark,themeName,viewport:{w:frame.w,h:frame.h},standalone});if(standalone){let inlined=await inlineStandaloneDocument(html,{assetRoot:p.folder.root,skipSizeWarning:!0});html=inlined.html,warnings.push(...inlined.warnings)}frames.push({frame,html,label:frame.label??screen.name??screen.id})}if(frames.length===0)throw Error(`board "${board.id}" has no exportable frames`);let composite=buildBoardComposite(frames,{labels:!0});return{html:composite.html,viewport:composite.viewport,warnings:dedupe(warnings)}}var dedupe=(list)=>[...new Set(list)];import{mkdir as mkdir7,writeFile as writeFile7}from"fs/promises";import{tmpdir}from"os";import{join as join12,resolve as resolve4}from"path";var EMPTY_MODULE=`export const components = {};
|
|
85
|
-
`;function hostAppRootFrom(folderRoot,hostApp){if(!hostApp?.root)return localDesignOf(folderRoot)?.appRoot??resolve4(folderRoot,"..");return
|
|
82
|
+
${darkBlock}`,diff=await diffFile(themePath,contents),applied=!1;if(options.apply&&!diff.identical)await mkdir4(dirname4(themePath),{recursive:!0}),await writeFile4(themePath,contents,"utf8"),applied=!0;let files=[{path:themePath,contents,diff,applied}],warnings=[];if(options.sourceTheme){let dtcg=await emitDtcgFile(options.sourceTheme,options);files.push(dtcg.file),warnings.push(...dtcg.warnings)}return{files,warnings,notes:[]}}var UTILITY_RENAMES=new Map([["shadow-xs","shadow-sm"],["shadow-sm","shadow"],["drop-shadow-xs","drop-shadow-sm"],["drop-shadow-sm","drop-shadow"],["blur-xs","blur-sm"],["blur-sm","blur"],["backdrop-blur-xs","backdrop-blur-sm"],["backdrop-blur-sm","backdrop-blur"],["outline-hidden","outline-none"]]),ROUNDED_RENAME=/^(rounded(?:-(?:s|e|t|r|b|l|ss|se|es|ee|tl|tr|br|bl))?)-(xs|sm)$/,GRADIENT_RENAME=/^bg-linear-to-(t|tr|r|br|b|bl|l|tl)$/,V4_ONLY_UTILITIES=[[/^inset-shadow(-|$)/,"inset shadows are v4-only \u2014 approximate with shadow-[inset_\u2026]"],[/^inset-ring(-|$)/,"inset rings are v4-only \u2014 approximate with shadow-[inset_\u2026]"],[/^field-sizing-/,"field-sizing is v4-only \u2014 needs custom CSS in v3"],[/^text-shadow(-|$)/,"text shadows are v4-only \u2014 needs custom CSS in v3"],[/^scheme-/,"color-scheme utilities are v4-only \u2014 needs custom CSS in v3"],[/^mask-/,"mask utilities are v4-only \u2014 needs custom CSS in v3"],[/^bg-(radial|conic)(-|$)/,"radial/conic gradient utilities are v4-only \u2014 use an arbitrary background-image in v3"],[/^bg-linear-/,"angle-based bg-linear-* is v4-only \u2014 v3 only has bg-gradient-to-<dir> or an arbitrary background-image"]],V4_ONLY_VARIANTS=[[/^starting$/,"the starting: variant (@starting-style) is v4-only"],[/^inert$/,"the inert: variant is v4-only"],[/^not-/,"the not-* variant is v4-only"],[/^in-/,"the in-* variant is v4-only"],[/^nth-/,"the nth-* variants are v4-only"],[/^\*\*$/,"the **: descendant variant is v4-only"],[/^@/,"container-query variants need the @tailwindcss/container-queries plugin (and a @container parent) in v3"]];function splitVariants(cls){let segments=[],depth=0,start=0;for(let i=0;i<cls.length;i++){let ch=cls[i];if(ch==="["||ch==="(")depth++;else if(ch==="]"||ch===")")depth=Math.max(0,depth-1);else if(ch===":"&&depth===0)segments.push(cls.slice(start,i)),start=i+1}return segments.push(cls.slice(start)),segments}function baseIssue(cls,variants,base){let negative=base.startsWith("-")?"-":"",important=base.endsWith("!"),bare=base.slice(negative.length,important?-1:void 0),withWrappers=(renamed)=>{let v3Base=`${important?"!":""}${negative}${renamed}`;return[...variants,v3Base].join(":")},renamed=UTILITY_RENAMES.get(bare);if(renamed!==void 0){let ringNote=bare==="outline-hidden"?"v3 outline-none keeps a transparent outline for forced-colors mode \u2014 usually the intent":"v4 shifted this size ladder down one step";return{class:cls,v3:withWrappers(renamed),note:ringNote}}if(bare==="ring")return{class:cls,v3:withWrappers("ring-1"),note:"v4 ring is 1px (v3 ring is 3px); v3 also defaults ring color to blue-500 \u2014 pair with an explicit ring-<color>"};if(bare==="ring-3")return{class:cls,v3:withWrappers("ring"),note:"v3 spells the 3px ring as bare `ring`; its default color is blue-500 \u2014 pair with an explicit ring-<color>"};let rounded=ROUNDED_RENAME.exec(bare);if(rounded){let v3Name=rounded[2]==="xs"?`${rounded[1]}-sm`:rounded[1];return{class:cls,v3:withWrappers(v3Name),note:"v4 shifted the rounded ladder down one step"}}let gradient=GRADIENT_RENAME.exec(bare);if(gradient)return{class:cls,v3:withWrappers(`bg-gradient-to-${gradient[1]}`),note:"v3 spells directional gradients bg-gradient-to-<dir>"};for(let[pattern,note]of V4_ONLY_UTILITIES)if(pattern.test(bare))return{class:cls,note};let varShorthand=/^([a-z][a-z0-9-]*)-\((--[a-zA-Z0-9-]+)\)$/.exec(bare);if(varShorthand)return{class:cls,v3:withWrappers(`${varShorthand[1]}-[var(${varShorthand[2]})]`),note:"the (--var) arbitrary-value shorthand is v4-only"};if(important)return{class:cls,v3:withWrappers(bare),note:"v3 puts the important marker first (`!utility`), v4 last (`utility!`)"};return null}function v3ClassIssues(classes){let issues=[],seen=new Set;for(let cls of classes){if(cls===""||seen.has(cls))continue;seen.add(cls);let segments=splitVariants(cls),base=segments[segments.length-1],variants=segments.slice(0,-1),variantHit=variants.flatMap((v)=>V4_ONLY_VARIANTS.filter(([p])=>p.test(v)).map(([,note])=>note)).at(0);if(variantHit!==void 0){issues.push({class:cls,note:variantHit});continue}let issue=baseIssue(cls,variants,base);if(issue)issues.push(issue)}return issues}function classNamesInJsx(jsx){let out=new Set,attr=/className="([^"]*)"/g,m=attr.exec(jsx);while(m!==null){for(let cls of m[1].split(/\s+/))if(cls!=="")out.add(cls);m=attr.exec(jsx)}return[...out]}var HSL_TRIPLET=/^-?[0-9.]+(?:deg)?\s+-?[0-9.]+%\s+-?[0-9.]+%(?:\s*\/\s*[0-9.]+%?)?$/,NON_FAMILY_FONT_ROLE=/^(size|weight|leading|tracking)(-|$)/,PALETTE_KEY=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;function paletteName(rawKey){let name=rawKey.startsWith("color-")?rawKey.slice(6):rawKey;if(SEMANTIC_SLOTS.has(name))return null;return PALETTE_KEY.test(name)?name:null}function isColorish(value){let v=value.trim().toLowerCase();if(HSL_TRIPLET.test(v))return!0;return/^#[0-9a-f]{3,8}$/.test(v)||/^(rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color)\(/.test(v)||v==="transparent"||v==="currentcolor"}function stripComments(css){let out="",from=0;for(;;){let open=css.indexOf("/*",from);if(open===-1)return out+css.slice(from);let close=css.indexOf("*/",open+2);if(close===-1)return out+css.slice(from);out+=css.slice(from,open),from=close+2}}function braceMatches(css){let matches=new Map,open=[];for(let i=0;i<css.length;i++)if(css[i]==="{")open.push(i),matches.set(i,-1);else if(css[i]==="}"){let at=open.pop();if(at!==void 0)matches.set(at,i)}return matches}var DECLARATION=/^--([a-zA-Z0-9-]+)\s*:([\s\S]*)$/;function scanBlocks(css,matches,into){let collecting=[],segmentStart=0;for(let i=0;i<css.length;i++){let ch=css[i];if(ch==="{")collecting.push(i>segmentStart&&matches(css.slice(segmentStart,i).trim()));else if(ch===";"){if(collecting[collecting.length-1]){let declaration=DECLARATION.exec(css.slice(segmentStart,i).trim()),value=declaration?.[2]?.trim();if(declaration&&value)into.set(declaration[1],value)}}else if(ch==="}")collecting.pop();else continue;segmentStart=i+1}}var DARK_SCHEME=/prefers-color-scheme\s*:\s*dark/;function splitDarkMedia(css){let darkVars=new Map,closing=braceMatches(css),out=css,from=0;for(;;){let at=css.indexOf("@media",from);if(at===-1)break;let start=at;while(start<css.length&&css[start]!=="{"&&css[start]!=="}")start++;if(from=start+1,css[start]!=="{"||!DARK_SCHEME.test(css.slice(at,start)))continue;let end=closing.get(start)??-1;if(end>start){let body=css.slice(start+1,end);scanBlocks(body,(p)=>p.startsWith(":root")||p==="*",darkVars),out=out.slice(0,at)+" ".repeat(end+1-at)+out.slice(end+1),from=end+1}}return{css:out,darkVars}}function resolveVars(value,scopes,depth=0){if(!value.includes("var("))return value;if(depth>4)return null;let unresolved=!1,replaced=value.replace(/var\(\s*--([a-zA-Z0-9-]+)\s*(?:,([^()]*))?\)/g,(_,name,fallback)=>{for(let scope of scopes){let v=scope.get(name);if(v!==void 0)return v}if(fallback!==void 0)return fallback.trim();return unresolved=!0,""});if(unresolved)return null;return resolveVars(replaced.trim(),scopes,depth+1)}function resolveCssVars(value,vars){return resolveVars(value,[vars])}function normalizeColor(value){let v=value.trim();return HSL_TRIPLET.test(v)?`hsl(${v})`:v}function lookupSlot(slot,declScope){return declScope.get(`color-${slot}`)??declScope.get(slot)}function extractColors(declScope,resolveScopes,scopeName,warnings){let colors={};for(let{key,pair}of COLOR_SLOTS){let raw=lookupSlot(key,declScope);if(raw===void 0)continue;let resolved=resolveVars(raw,resolveScopes);if(resolved===null||resolved===""){warnings.push(`${scopeName}: --${key} references an undefined var \u2014 skipped`);continue}let value=normalizeColor(resolved);if(!pair){colors[key]=value;continue}let fgRaw=lookupSlot(`${key}-foreground`,declScope),fg=fgRaw!==void 0?resolveVars(fgRaw,resolveScopes):null;colors[key]=fg!==null&&fg!==""&&fgRaw!==void 0?{DEFAULT:value,foreground:normalizeColor(fg)}:value}return colors}function extractPalette(declScope,resolveScopes){let palette={};for(let[rawKey,raw]of declScope){let name=paletteName(rawKey);if(name===null)continue;if(rawKey.startsWith("color-")||!declScope.has(`color-${rawKey}`)){let resolved=resolveVars(raw,resolveScopes);if(resolved===null||resolved===""||!isColorish(resolved))continue;palette[name]=normalizeColor(resolved)}}return palette}function parseThemeCss(css){let warnings=[],stripped=stripComments(css),{css:lightCss,darkVars:mediaDarkVars}=splitDarkMedia(stripped),rootVars=new Map;scanBlocks(lightCss,(p)=>p.startsWith(":root")||p.startsWith("@theme")||p.startsWith("html"),rootVars);let darkVars=mediaDarkVars;scanBlocks(lightCss,(p)=>/(^|[\s,])\.dark(\s|,|:|$)/.test(p)||p===".dark",darkVars);let colors=extractColors(rootVars,[rootVars],"light",warnings),colorsDark=darkVars.size>0?extractColors(darkVars,[darkVars,rootVars],"dark",warnings):{},palette=extractPalette(rootVars,[rootVars]),paletteDark=darkVars.size>0?extractPalette(darkVars,[darkVars,rootVars]):{},result={colors,colorsDark,palette,paletteDark,warnings,rootVars},radiusRaw=rootVars.get("radius");if(radiusRaw!==void 0){let radius=resolveVars(radiusRaw,[rootVars]);if(radius!==null&&radius!=="")result.radius=radius;else warnings.push("--radius references an undefined var \u2014 skipped")}let fontFamily={};for(let[name,value]of rootVars){let m=/^font-([a-z][a-z0-9-]*)$/.exec(name);if(!m)continue;let role=m[1];if(NON_FAMILY_FONT_ROLE.test(role))continue;let resolved=resolveVars(value,[rootVars]);if(resolved!==null&&resolved!=="")fontFamily[role]=resolved}if(Object.keys(fontFamily).length>0)result.fontFamily=fontFamily;return result}function matchBrace(src,open){let depth=0;for(let i=open;i<src.length;i++)if(src[i]==="{")depth++;else if(src[i]==="}"&&--depth===0)return i;return-1}function stripComments2(src){return src.replace(/\/\/[^\n]*/g,"").replace(/\/\*[\s\S]*?\*\//g,"")}function objectBody(src,keyRe){let key=src.search(keyRe);if(key===-1)return null;let open=src.indexOf("{",key);if(open===-1)return null;let end=matchBrace(src,open);if(end===-1)return null;return src.slice(open+1,end)}function parseTailwindContainer(src){let stripped=stripComments2(src),key=stripped.search(/\bcontainer\s*:\s*\{/);if(key===-1)return null;let open=stripped.indexOf("{",key),end=matchBrace(stripped,open);if(end===-1)return null;let body=stripped.slice(open+1,end),out={},center=/\bcenter\s*:\s*(true|false)/.exec(body);if(center)out.center=center[1]==="true";let padStr=/\bpadding\s*:\s*["'`]([^"'`]+)["'`]/.exec(body);if(padStr?.[1]!==void 0)out.padding=padStr[1];else{let padObj=/\bpadding\s*:\s*\{[^}]*?DEFAULT\s*:\s*["'`]([^"'`]+)["'`]/.exec(body);if(padObj?.[1]!==void 0)out.padding=padObj[1]}let screensBody=/\bscreens\s*:\s*\{([^}]*)\}/.exec(body)?.[1];if(screensBody){let pxs=[...screensBody.matchAll(/:\s*["'`](\d+)px["'`]/g)].map((m)=>Number(m[1])).filter((n)=>!Number.isNaN(n));if(pxs.length>0)out.maxWidth=`${Math.max(...pxs)}px`}return Object.keys(out).length>0?out:null}function paddingClass(p){let rem=/^([\d.]+)rem$/.exec(p),px=/^([\d.]+)px$/.exec(p),units=rem?Number(rem[1])*4:px?Number(px[1])/4:null;return units!==null&&Number.isInteger(units)&&units>=0?`px-${units}`:`px-[${p}]`}function containerClasses(c){let parts=[c.center?"mx-auto":"","w-full"];if(c.padding)parts.push(paddingClass(c.padding));if(c.maxWidth)parts.push(`max-w-[${c.maxWidth}]`);return parts.filter(Boolean).join(" ")}var SAFE_KEY=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;function unquote(raw){let m=/^(["'`])([\s\S]*)\1$/.exec(raw.trim());return m?m[2]:null}function topLevelEntries(body){let entries=[],n=body.length,i=0;while(i<n){while(i<n&&/[\s,;]/.test(body[i]))i++;if(i>=n)break;let key,c0=body[i];if(c0==='"'||c0==="'"||c0==="`"){let j=i+1;while(j<n&&body[j]!==c0)j++;key=body.slice(i+1,j),i=j+1}else{let j=i;while(j<n&&/[A-Za-z0-9_$-]/.test(body[j]))j++;key=body.slice(i,j),i=j}while(i<n&&/\s/.test(body[i]))i++;if(body[i]!==":"||key===""){i=skipValue(body,i);continue}i++;while(i<n&&/\s/.test(body[i]))i++;let start=i;i=skipValue(body,i),entries.push([key,body.slice(start,i).trim()]),i++}return entries}function skipValue(body,from){let n=body.length,i=from,depth=0,str=null;while(i<n){let c=body[i];if(str){if(c==="\\")i++;else if(c===str)str=null}else if(c==='"'||c==="'"||c==="`")str=c;else if(c==="{"||c==="["||c==="(")depth++;else if(c==="}"||c==="]"||c===")")depth--;else if(c===","&&depth===0)break;i++}return i}function stringArray(raw){return[...raw.matchAll(/(["'`])([\s\S]*?)\1/g)].map((m)=>m[2])}function parseStringRecord(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let value=unquote(raw);if(value===null)continue;let k=key.toLowerCase();if(SAFE_KEY.test(k))out[k]=value}return Object.keys(out).length>0?out:void 0}function parseColors(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let name=key.toLowerCase(),value=unquote(raw);if(value!==null){if(SAFE_KEY.test(name))out[name]=value;continue}if(raw.startsWith("{")){let inner=objectBody(raw,/^\s*\{/);if(!inner)continue;for(let[sub,subRaw]of topLevelEntries(inner)){let subVal=unquote(subRaw),flat=`${name}-${sub.toLowerCase()}`;if(subVal!==null&&SAFE_KEY.test(flat))out[flat]=subVal}}}return Object.keys(out).length>0?out:void 0}function parseFontFamily(body){if(body===null)return;let out={};for(let[key,raw]of topLevelEntries(body)){let k=key.toLowerCase();if(!SAFE_KEY.test(k))continue;let single=unquote(raw);if(single!==null)out[k]=single;else if(raw.startsWith("[")){let stack=stringArray(raw).join(", ");if(stack)out[k]=stack}}return Object.keys(out).length>0?out:void 0}var SAFE_STEP=/^(from|to|[\d.%,\s]+)$/i,SAFE_PROP=/^-{0,2}[a-z][a-z0-9-]*$/,SAFE_IDENT=/^[A-Za-z][A-Za-z0-9_-]*$/;function kebab(prop){return prop.replace(/[A-Z]/g,(m)=>`-${m.toLowerCase()}`)}function parseKeyframes(body){if(body===null)return;let out={};for(let[name,rawObj]of topLevelEntries(body)){if(!SAFE_IDENT.test(name)||!rawObj.startsWith("{"))continue;let stepsBody=objectBody(rawObj,/^\s*\{/);if(!stepsBody)continue;let steps={};for(let[step,rawDecls]of topLevelEntries(stepsBody)){if(!SAFE_STEP.test(step.trim())||!rawDecls.startsWith("{"))continue;let declsBody=objectBody(rawDecls,/^\s*\{/);if(!declsBody)continue;let decls={};for(let[prop,rawVal]of topLevelEntries(declsBody)){let cssProp=kebab(prop),value=unquote(rawVal);if(value===null||/[{};]/.test(value)||!SAFE_PROP.test(cssProp))continue;decls[cssProp]=value}if(Object.keys(decls).length>0)steps[step.trim()]=decls}if(Object.keys(steps).length>0)out[name]=steps}return Object.keys(out).length>0?out:void 0}function parseThemeExtend(src){let extend=objectBody(stripComments2(src),/\bextend\s*:\s*\{/);if(extend===null)return null;let out={},colors=parseColors(objectBody(extend,/\bcolors\s*:\s*\{/));if(colors)out.colors=colors;let spacing=parseStringRecord(objectBody(extend,/\bspacing\s*:\s*\{/));if(spacing)out.spacing=spacing;let boxShadow=parseStringRecord(objectBody(extend,/\bboxShadow\s*:\s*\{/));if(boxShadow)out.boxShadow=boxShadow;let fontFamily=parseFontFamily(objectBody(extend,/\bfontFamily\s*:\s*\{/));if(fontFamily)out.fontFamily=fontFamily;let keyframes=parseKeyframes(objectBody(extend,/\bkeyframes\s*:\s*\{/));if(keyframes)out.keyframes=keyframes;let animation=parseStringRecord(objectBody(extend,/\banimation\s*:\s*\{/));if(animation)out.animation=animation;return Object.keys(out).length>0?out:null}import{mkdir as mkdir5,rename,writeFile as writeFile5}from"fs/promises";import{dirname as dirname5,join as join6}from"path";async function writeJsonAtomic(path,value){await mkdir5(dirname5(path),{recursive:!0});let tmp=`${path}.${process.pid}.${Date.now()}.tmp`,body=`${JSON.stringify(value,null,2)}
|
|
83
|
+
`;await writeFile5(tmp,body,"utf8"),await rename(tmp,path)}async function writeText(path,body){await mkdir5(dirname5(path),{recursive:!0}),await writeFile5(path,body,"utf8")}function pngSize2(bytes){if(bytes.length<24||bytes.toString("latin1",12,16)!=="IHDR")return null;let width=bytes.readUInt32BE(16),height=bytes.readUInt32BE(20);return width>0&&height>0?{width,height}:null}var ALLOWED_ASSET_EXTENSIONS=new Set([".png",".jpg",".jpeg",".gif",".webp",".avif",".svg",".ico",".bmp",".woff",".woff2",".ttf",".otf"]),ASSET_MIME={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".bmp":"image/bmp",".woff":"font/woff",".woff2":"font/woff2",".ttf":"font/ttf",".otf":"font/otf"};function extOf(filename){let dot=filename.lastIndexOf(".");return dot===-1?"":filename.slice(dot).toLowerCase()}function isAllowedAssetExt(filename){return ALLOWED_ASSET_EXTENSIONS.has(extOf(filename))}function sanitizeFilename(filename){return(filename.split(/[/\\]/).pop()??"asset").replace(/\.\./g,"_").replace(/[^a-zA-Z0-9._-]/g,"_").replace(/^[._-]+/,"").replace(/_+/g,"_")||"asset"}async function storeAsset(root,filename,bytes){let safe=sanitizeFilename(filename);if(!isAllowedAssetExt(safe))throw Error(`storeAsset: ${safe} is not an allowed image/font asset type`);let dir=join6(root,"assets");await mkdir5(dir,{recursive:!0});let out=/\.svg$/i.test(safe)?Buffer.from(sanitizeSvgMarkup(bytes.toString("utf8")),"utf8"):bytes;return await writeFile5(join6(dir,safe),out),{assetPath:`assets/${safe}`,url:`/assets/${safe}`,bytes:out.length}}var TAILWIND_CLASSNAME={kind:"tailwind-classname",prop:"className",needsTailwindJit:!0,editorLabel:"Tailwind classes"},SX_PROP={kind:"sx",prop:"sx",needsTailwindJit:!1,editorLabel:"sx props"},STYLE_PROP={kind:"style",prop:"style",needsTailwindJit:!1,editorLabel:"Inline styles"},STYLE_CHANNELS={"tailwind-classname":TAILWIND_CLASSNAME,sx:SX_PROP,style:STYLE_PROP},CSS_FRAMEWORK_CHANNEL={tailwind:"tailwind-classname",none:"style"};function catalogFromManifest(manifest,opts){return manifest.map((descriptor)=>({id:descriptor.id,installed:opts.installed??!0,importPath:opts.importPath}))}function identifierRef(path){if(!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(path))throw Error(`identifierRef: "${path}" is not a bare identifier path`);return{$identifier:path}}function styleChannelOf(p,folderCss){let adapter=p,allowed=adapter.styleChannels??[adapter.styleChannel?.kind??"tailwind-classname"],fallback=allowed[0]??"tailwind-classname";if(folderCss){let wanted=CSS_FRAMEWORK_CHANNEL[folderCss];if(allowed.includes(wanted))return STYLE_CHANNELS[wanted]}return STYLE_CHANNELS[fallback]}class UnknownProviderError extends Error{id;constructor(id,message){super(message??`Unknown component provider: "${id}"`);this.name="UnknownProviderError",this.id=id}}function createProviderLoader(factories){return async(library)=>{let factory=factories[library.id];if(!factory){let known=Object.keys(factories).sort();throw new UnknownProviderError(library.id,`Unknown component provider: "${library.id}". Known providers: ${known.join(", ")}.`)}return factory(library)}}var COMPONENT_GROUPS=[{id:"actions",label:"Actions"},{id:"forms",label:"Forms & Inputs"},{id:"display",label:"Display"},{id:"feedback",label:"Feedback"},{id:"navigation",label:"Navigation"},{id:"overlays",label:"Overlays"},{id:"layout",label:"Layout"},{id:"typography",label:"Typography"},{id:"visuals",label:"Visuals"},{id:"chat",label:"Chat & AI"}],UNGROUPED_LABEL="Components";import{existsSync as existsSync2,lstatSync,readdirSync,readFileSync as readFileSync2,realpathSync}from"fs";import{rm}from"fs/promises";import{homedir}from"os";import{basename as basename2,dirname as dirname6,join as join7,resolve}from"path";var ID=/^[A-Za-z0-9_-]{8,64}$/,RecordSchema=object({root:string().min(1),appRoot:string().min(1),projectName:string().optional(),designPath:string().min(1).optional()});function storageRoot(){return resolve(process.env.VELLOO_DESIGNS_HOME??join7(homedir(),".velloo","designs"))}function locationsDir(){return join7(storageRoot(),".locations")}function realOr(path){return existsSync2(path)?realpathSync(path):resolve(path)}function managedDesignPath(id){if(!ID.test(id))throw Error(`Invalid managed design identifier: ${JSON.stringify(id)}`);let folder=join7(storageRoot(),id);if(lstatSync(folder,{throwIfNoEntry:!1})?.isSymbolicLink())throw Error(`Managed design ${folder} is a symbolic link. Restore the actual folder here before opening it.`);return folder}function managedDesignId(folder){let abs=resolve(folder);if(realOr(dirname6(abs))!==realOr(storageRoot()))return null;let id=basename2(abs);return ID.test(id)?id:null}var DevRecordSchema=object({manifestPath:string().min(1),appRoot:string().min(1),projectName:string().optional()});function readRecord(path){let raw=JSON.parse(readFileSync2(path,"utf8")),current=RecordSchema.safeParse(raw);if(current.success)return current.data;let dev=DevRecordSchema.parse(raw),migrated=RecordSchema.parse({root:dirname6(resolve(dev.manifestPath)),appRoot:resolve(dev.appRoot),...dev.projectName?{projectName:dev.projectName}:{}});return writeJsonAtomic(path,migrated).catch(()=>{}),migrated}function listLocalDesigns(){let names;try{names=readdirSync(locationsDir()).filter((name)=>name.endsWith(".json"))}catch{return[]}let out=[];for(let name of names.sort()){let id=name.slice(0,-5);if(!ID.test(id))continue;try{let record=readRecord(join7(locationsDir(),name));out.push({id,root:record.root,appRoot:record.appRoot,legacyName:record.projectName,designRoot:record.designPath??managedDesignPath(id)})}catch{}}return out}function localDesignsAt(dir){let here=realOr(dir);return listLocalDesigns().filter((design)=>realOr(design.root)===here)}function localDesignOf(folder){let target=realOr(folder);return listLocalDesigns().find((design)=>realOr(design.designRoot)===target)??null}async function writeLocalDesign(id,record){if(!ID.test(id))throw Error(`Invalid local design identifier: ${JSON.stringify(id)}`);try{await writeJsonAtomic(join7(locationsDir(),`${id}.json`),RecordSchema.parse({root:resolve(record.root),appRoot:resolve(record.appRoot),...record.designPath?{designPath:resolve(record.designPath)}:{}}))}catch(error){throw Error(`Could not save the local design record ${id}. Check write permissions and free space in ${storageRoot()}, then retry. Design content was retained. ${error instanceof Error?error.message:error}`)}}async function removeLocalDesign(id){if(!ID.test(id))return;await rm(join7(locationsDir(),`${id}.json`),{force:!0})}function resolveAppPath(folder,path){if(!path.startsWith(APP_PATH_PREFIX))return resolve(folder,path);let design=localDesignOf(folder);if(!design)throw Error(`App-relative reference ${path} has no application on this machine for ${folder}. Run \`velloo design bind ${folder}\` from the application checkout.`);let rel=path.slice(APP_PATH_PREFIX.length);if(/^(?:[\\/]|[A-Za-z]:)/.test(rel))throw Error(`Invalid app-relative path: ${path}`);return resolve(design.appRoot,rel.replace(/\\/g,"/"))}import{existsSync as existsSync3,readFileSync as readFileSync3,realpathSync as realpathSync2}from"fs";import{readFile as readFile2}from"fs/promises";import{basename as basename3,dirname as dirname7,join as join8,relative,resolve as resolve2,sep}from"path";async function readRepoManifestAt(dir,opts={}){let at=resolve2(dir),path=join8(at,REPO_MANIFEST_FILE),raw;try{raw=await readFile2(path,"utf8")}catch(err){if(err.code!=="ENOENT"&&opts.strict)throw err;return null}try{return parseManifest(path,at,raw)}catch(err){if(opts.strict)throw err;return null}}async function findOwningManifest(folder,opts={}){let target=realOr2(folder);for(let dir=resolve2(folder);;dir=dirname7(dir)){let found=await readRepoManifestAt(dir,opts);if(found?.folders.some((listed)=>realOr2(listed)===target))return found;if(dirname7(dir)===dir)return null}}function parseManifest(path,dir,raw){let parsed;try{parsed=JSON.parse(raw)}catch(err){throw Error(`${path} is not valid JSON: ${err.message}`)}let projects=parsed?.projects;for(let[name,entry]of Object.entries(projects&&typeof projects==="object"?projects:{}))if(entry&&typeof entry==="object"&&"managed"in entry)throw Error(`${path}: "${name}" is a managed design locator, which velloo.json no longer holds \u2014 a design outside the repository is recorded only on the machine that has it. Remove the entry, then run \`velloo design bind <design folder>\` from this checkout.`);let normalized=normalizeRepoManifest(parsed),result=RepoManifestSchema.safeParse(normalized.manifest);if(!result.success){let issues=result.error.issues.map((i)=>`${i.path.join(".")||"(root)"}: ${i.message}`).join("; ");throw Error(`${path} is not a valid velloo manifest: ${issues}`)}let absolute=(rel)=>resolve2(dir,rel.replace(/\\/g,"/"));return{path,dir,manifest:result.data,folders:result.data.designs.map(absolute),legacy:normalized.legacy,legacyNames:new Map(Object.entries(normalized.legacyNames).map(([rel,name])=>[absolute(rel),name]))}}function recordedDesignName(folder){try{let raw=JSON.parse(readFileSync3(join8(folder,".design","config.json"),"utf8"));return typeof raw.name==="string"&&isDesignName(raw.name)?raw.name:null}catch{return null}}function isUnder(parent,child){return child===parent||child.startsWith(parent+sep)}function realOr2(path){return existsSync3(path)?realpathSync2(path):resolve2(path)}async function findDesigns2(cwd,opts={}){let repo=await readRepoManifestAt(cwd,opts),local=localDesignsAt(cwd);if(!repo&&local.length===0)return null;let entries=[],seen=new Set,repoReal=repo?realOr2(repo.dir):null,add=(root,record,fallback)=>{let real=existsSync3(root)||!repo||!repoReal?realOr2(root):resolve2(repoReal,relative(repo.dir,root));if(seen.has(real))return;seen.add(real);let outsideRepo=!record&&repoReal!==null&&!isUnder(repoReal,real),exists=existsSync3(join8(root,".design","config.json")),name=recordedDesignName(root)??fallback??toDesignName(basename3(root))??"design";entries.push({name,root,local:record,exists,outsideRepo})};for(let folder of repo?.folders??[])add(folder,null,repo?.legacyNames.get(folder));for(let design of local)add(design.designRoot,design,design.legacyName);entries.sort((a,b)=>a.name.localeCompare(b.name));let byName=new Map,conflicts=new Map;for(let entry of entries){let clash=byName.get(entry.name);if(clash)conflicts.set(entry.name,[...conflicts.get(entry.name)??[clash],entry]);else byName.set(entry.name,entry)}for(let name of conflicts.keys())byName.delete(name);return{repo,local,designs:entries,byName,conflicts,defaultDesign:repo?.manifest.defaultDesign}}async function designsFor(designRoot){let local=localDesignOf(designRoot);if(local)return findDesigns2(local.root);let owner=await findOwningManifest(designRoot);return owner?findDesigns2(owner.dir):null}function pickDesign(set,cwd){let here=realOr2(cwd),present=set.designs.filter((d)=>d.exists&&!d.outsideRepo),usable=present.filter((d)=>set.byName.get(d.name)===d),standingIn=usable.find((d)=>realOr2(d.root)===here);if(standingIn)return{design:standingIn,reason:"cwd"};if(usable.length===1&&usable[0]&&present.length===1)return{design:usable[0],reason:"only"};let byDefault=set.defaultDesign?set.byName.get(set.defaultDesign):void 0;if(byDefault?.exists)return{design:byDefault,reason:"default"};return present[0]?{design:present[0],reason:"arbitrary"}:null}async function designRenameConflict(root,name){let issue=designNameIssue(name);if(issue)return`${issue[0]?.toUpperCase()}${issue.slice(1)}.`;let set=await designsFor(root),here=realOr2(root),holder=set?.designs.find((d)=>d.name===name&&realOr2(d.root)!==here);if(holder)return`A design named "${name}" already exists at ${holder.root}.`;if(set?.repo?.legacy)return`${set.repo.path} is in the pre-designs format \u2014 run \`velloo upgrade\` first.`;return null}async function followDesignRename(root,previous,name){let repo=(await designsFor(root))?.repo;if(!repo||repo.legacy||repo.manifest.defaultDesign!==previous)return!1;return await writeJsonAtomic(repo.path,{...repo.manifest,defaultDesign:name}),!0}async function findRepoManifest(startDir){if(localDesignOf(startDir))return null;return await findOwningManifest(startDir)??readRepoManifestAt(startDir)}async function readRepoFeedback(startDir){let feedback=(await findRepoManifest(startDir))?.manifest.feedback;return feedback?{enabled:feedback.enabled}:null}async function writeRepoFeedback(startDir,feedback){let found=await findRepoManifest(startDir);if(!found)return null;if(found.legacy)throw Error(`${found.path} is in the pre-designs format \u2014 run \`velloo upgrade\` to migrate it first.`);return await writeJsonAtomic(found.path,{...found.manifest,feedback:{enabled:feedback.enabled}}),found.path}import{mkdir as mkdir6,readFile as readFile3,writeFile as writeFile6}from"fs/promises";import{homedir as homedir2}from"os";import{dirname as dirname8,join as join9}from"path";var prefsPath=()=>process.env.VELLOO_PREFS_PATH??join9(homedir2(),".velloo","prefs.json");async function readAll(){try{let raw=JSON.parse(await readFile3(prefsPath(),"utf8"));if(raw.version===1)return raw}catch{}return{version:1}}async function readFeedbackContactOk(){return(await readAll()).feedbackContactOk===!0}async function writeFeedbackContactOk(contactOk){let next={...await readAll(),version:1,feedbackContactOk:contactOk},path=prefsPath();await mkdir6(dirname8(path),{recursive:!0}),await writeFile6(path,`${JSON.stringify(next,null,2)}
|
|
84
|
+
`,"utf8")}import{readdir,readFile as readFile4}from"fs/promises";import{basename as basename4,extname,join as join10,resolve as resolve3}from"path";function keyOf(e){if(e.kind==="screen")return`screen:${e.screenId}${suffix(e.coalesceKey)}`;if(e.kind==="board")return`board:${e.boardId}`;if(e.kind==="boards")return null;if(e.kind==="snippet")return`snippet:${e.snippetId}${suffix(e.coalesceKey)}`;if(!e.coalesceKey)return null;return`theme:${e.themeName}:${e.coalesceKey}`}function suffix(coalesceKey){return coalesceKey?`:${coalesceKey}`:""}class HistoryManager{undoStack=[];redoStack=[];push(entry){let stamped={...entry,ts:Date.now()},top=this.undoStack[this.undoStack.length-1],key=keyOf(stamped),named="coalesceKey"in entry&&entry.coalesceKey!==void 0,inWindow=top?.ts!==void 0&&stamped.ts!==void 0&&stamped.ts-top.ts<800;if(key!==null&&top!==void 0&&(named||inWindow)){if(keyOf(top)===key){top.ts=stamped.ts,this.redoStack.length=0;return}}if(this.undoStack.push(stamped),this.undoStack.length>50)this.undoStack.splice(0,this.undoStack.length-50);this.redoStack.length=0}pushRedo(entry){if(this.redoStack.push(entry),this.redoStack.length>50)this.redoStack.splice(0,this.redoStack.length-50)}pushUndoSilent(entry){if(this.undoStack.push(entry),this.undoStack.length>50)this.undoStack.splice(0,this.undoStack.length-50)}popUndo(){return this.undoStack.pop()}popRedo(){return this.redoStack.pop()}depths(){return{undo:this.undoStack.length,redo:this.redoStack.length}}truncateUndoTo(undoDepth){while(this.undoStack.length>undoDepth)this.undoStack.pop()}}function screenIdFromFilename(filename){return basename4(filename,extname(filename))}function boardIdFromFilename(filename){return basename4(filename,extname(filename))}function snippetIdFromFilename(filename){return basename4(filename,extname(filename))}async function readJson(path){let raw=await readFile4(path,"utf8");return JSON.parse(raw)}async function readJsonOrNull(path){try{return await readJson(path)}catch(err){if(err.code==="ENOENT")return null;throw err}}async function loadDir(dir,parse,idFromFile,predicate){let out=new Map,entries;try{entries=await readdir(dir)}catch(err){if(err.code==="ENOENT")return out;throw err}entries.sort((a,b)=>a.localeCompare(b));for(let file of entries){if(extname(file)!==".json")continue;if(predicate&&!predicate(file))continue;let raw=await readJson(join10(dir,file));out.set(idFromFile(file),parse(raw))}return out}async function loadAnnotations(root,screenIds){let annotations=new Map;for(let screenId of screenIds){let raw=await readJsonOrNull(join10(root,"screens",`${screenId}.annotations.json`));annotations.set(screenId,raw===null?[]:raw.map((r)=>AnnotationSchema.parse(r)))}return annotations}async function loadBoardNotes(root,boardIds){let notes=new Map;for(let boardId of boardIds){let raw=await readJsonOrNull(join10(root,"boards",`${boardId}.notes.json`));notes.set(boardId,raw===null?[]:raw.map((r)=>CanvasNoteSchema.parse(r)))}return notes}function isPlainJson(file){return!basename4(file,".json").includes(".")}async function loadDesignFolder(folder,options={}){let root=resolve3(folder),configRaw=await readJson(join10(root,".design","config.json")),themeRaw=await readJson(join10(root,"theme","default.json")),version=schemaVersionOf(configRaw);if(version>CURRENT_SCHEMA_VERSION)throw Error(`velloo: ${root} uses design-folder schema version ${version}, but this velloo only knows version ${CURRENT_SCHEMA_VERSION}. Upgrade velloo to open it.`);if(version<CURRENT_SCHEMA_VERSION)throw Error(`velloo: ${root} uses design-folder schema version ${version} (current: ${CURRENT_SCHEMA_VERSION}). Run \`velloo upgrade ${root}\` to migrate it.`);let parsedConfig=ConfigSchema.parse(configRaw),feedback=(options.preferences===!1?null:await readRepoFeedback(root))??parsedConfig.feedback,config=feedback?{...parsedConfig,feedback:{...feedback,contactOk:await readFeedbackContactOk()}}:parsedConfig,theme=ThemeSchema.parse(themeRaw),screens=await loadDir(join10(root,"screens"),(raw)=>ScreenSchema.parse(raw),screenIdFromFilename,isPlainJson),snippets=await loadDir(join10(root,"snippets"),(raw)=>SnippetSchema.parse(raw),snippetIdFromFilename),boards=await loadDir(join10(root,"boards"),(raw)=>BoardSchema.parse(raw),boardIdFromFilename,isPlainJson),annotations=await loadAnnotations(root,screens.keys()),notes=await loadBoardNotes(root,boards.keys()),customCss=await readCustomCss(root),themes=await loadDir(join10(root,"theme"),(raw)=>ThemeSchema.parse(raw),snippetIdFromFilename);return themes.set("default",theme),{root,config,theme,history:new HistoryManager,customCss,themes,screens,boards,snippets,annotations,notes}}function orderedBoards(folder){let order=folder.config.boardOrder;if(!order||order.length===0)return[...folder.boards.entries()];let seen=new Set,out=[];for(let id of order){let board=folder.boards.get(id);if(board&&!seen.has(id))out.push([id,board]),seen.add(id)}for(let[id,board]of folder.boards)if(!seen.has(id))out.push([id,board]);return out}function activeBoards(folder){return orderedBoards(folder).filter(([,board])=>!isArchived(board))}async function reloadScreen(folder,screenId){let path=join10(folder.root,"screens",`${screenId}.json`);try{let raw=await readJson(path),screen=ScreenSchema.parse(raw);return folder.screens.set(screenId,screen),screen}catch(err){if(err.code==="ENOENT")return folder.screens.delete(screenId),folder.annotations.delete(screenId),null;throw err}}async function reloadBoard(folder,boardId){let path=join10(folder.root,"boards",`${boardId}.json`);try{let raw=await readJson(path),board=BoardSchema.parse(raw);return folder.boards.set(boardId,board),board}catch(err){if(err.code==="ENOENT")return folder.boards.delete(boardId),folder.notes.delete(boardId),null;throw err}}async function reloadTheme(folder){let raw=await readJson(join10(folder.root,"theme","default.json")),theme=ThemeSchema.parse(raw);return folder.theme=theme,folder.customCss=await readCustomCss(folder.root),folder.themes=await loadDir(join10(folder.root,"theme"),(r)=>ThemeSchema.parse(r),snippetIdFromFilename),folder.themes.set("default",theme),theme}function themeByName(folder,name){if(!name)return folder.theme;return folder.themes.get(name)??folder.theme}function resolveNamedTheme(folder,name){if(!name)return{ok:!0,theme:folder.theme};let theme=folder.themes.get(name);if(theme)return{ok:!0,theme};let known=[...folder.themes.keys()].join(", ")||"(none)";return{ok:!1,message:`Unknown theme "${name}". Known themes: ${known}.`}}function pinnedThemeForScreen(folder,screenId){let boardsByTheme=new Map;for(let board of folder.boards.values()){if(isArchived(board))continue;if(!board.frames.some((f)=>f.screen===screenId))continue;let name=board.theme??"default",hosts=boardsByTheme.get(name)??[];hosts.push(board.id),boardsByTheme.set(name,hosts)}if(boardsByTheme.size===0)return{ok:!0,name:void 0};if(boardsByTheme.size===1){let[only]=boardsByTheme.keys();return{ok:!0,name:only==="default"?void 0:only}}let list=[...boardsByTheme.entries()].map(([name,hosts])=>`"${name}" (${hosts.join(", ")})`).join(", ");return{ok:!1,message:`Screen "${screenId}" is hosted by boards with different themes: ${list}. Pass theme: to pick the one to render.`}}function pinnedSchemeForScreen(folder,screenId){let hostsByScheme=new Map;for(let board of folder.boards.values()){if(isArchived(board))continue;for(let frame of board.frames){if(frame.screen!==screenId)continue;let scheme=frame.scheme??"canvas default",hosts=hostsByScheme.get(scheme)??[];hosts.push(`${board.id}/${frame.id}`),hostsByScheme.set(scheme,hosts)}}if(hostsByScheme.size===0)return{ok:!0,scheme:void 0};if(hostsByScheme.size===1){let[only]=hostsByScheme.keys();return{ok:!0,scheme:only==="canvas default"?void 0:only}}let list=[...hostsByScheme.entries()].map(([scheme,hosts])=>`"${scheme}" (${hosts.join(", ")})`).join(", ");return{ok:!1,message:`Screen "${screenId}" is hosted by frames with different color schemes: ${list}. Pass mode: "light" or "dark" to pick the one to render.`}}async function readCustomCss(root){try{return await readFile4(join10(root,"theme","custom.css"),"utf8")}catch(err){if(err.code==="ENOENT")return"";throw err}}async function reloadSnippet(folder,snippetId){let path=join10(folder.root,"snippets",`${snippetId}.json`);try{let raw=await readJson(path),snippet=SnippetSchema.parse(raw);return folder.snippets.set(snippetId,snippet),snippet}catch(err){if(err.code==="ENOENT")return folder.snippets.delete(snippetId),null;throw err}}async function reloadAnnotations(folder,screenId){let raw=await readJsonOrNull(join10(folder.root,"screens",`${screenId}.annotations.json`)),parsed=raw===null?[]:raw.map((r)=>AnnotationSchema.parse(r));return folder.annotations.set(screenId,parsed),parsed}async function reloadNotes(folder,boardId){let raw=await readJsonOrNull(join10(folder.root,"boards",`${boardId}.notes.json`)),parsed=raw===null?[]:raw.map((r)=>CanvasNoteSchema.parse(r));return folder.notes.set(boardId,parsed),parsed}var import_react3=__toESM(require_react_development(),1);var React4=__toESM(require_react_development(),1),jsx_dev_runtime10=__toESM(require_react_jsx_dev_runtime_development(),1),MAX_LABEL_CHARS=80;function summarizeValue(value){if(value===null)return"null";if(value===void 0)return"\u2014";if(typeof value==="string")return value.length>MAX_LABEL_CHARS?`"${value.slice(0,MAX_LABEL_CHARS-1)}\u2026"`:`"${value}"`;if(typeof value==="number"||typeof value==="boolean")return String(value);if(Array.isArray(value))return`[${value.length}]`;if(typeof value==="object")return"{}";return String(value)}function pickShownProps(declared,resolved){let out=[];for(let prop of declared){if(prop.name in resolved)out.push({name:prop.name,value:summarizeValue(resolved[prop.name])});else if(prop.defaultValue!==void 0)out.push({name:prop.name,value:prop.defaultValue});if(out.length>=6)break}return out}function ExtensionPlaceholder({id,extension,resolvedProps,className,...rest}){let shown=pickShownProps(extension.props,resolvedProps),trimmedClassName=["flex flex-col gap-1.5 rounded-md border border-dashed border-border bg-muted/40 px-4 py-3 text-card-foreground",className].filter(Boolean).join(" ");return jsx_dev_runtime10.jsxDEV("div",{"data-velloo-extension":id,"data-velloo-extension-path":extension.importPath,className:trimmedClassName,...rest,children:[jsx_dev_runtime10.jsxDEV("div",{className:"flex items-baseline justify-between gap-2",children:[jsx_dev_runtime10.jsxDEV("span",{className:"font-mono text-xs font-semibold text-foreground",children:id},void 0,!1,void 0,this),jsx_dev_runtime10.jsxDEV("span",{className:"font-mono text-[10px] uppercase tracking-wider text-muted-foreground",children:"extension"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),extension.description?jsx_dev_runtime10.jsxDEV("div",{className:"text-xs text-muted-foreground",children:extension.description},void 0,!1,void 0,this):null,shown.length>0?jsx_dev_runtime10.jsxDEV("div",{className:"mt-1 grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-0.5 font-mono text-[11px] leading-tight",children:shown.map((row)=>jsx_dev_runtime10.jsxDEV(React4.Fragment,{children:[jsx_dev_runtime10.jsxDEV("span",{className:"text-muted-foreground",children:row.name},void 0,!1,void 0,this),jsx_dev_runtime10.jsxDEV("span",{className:"truncate",children:row.value},void 0,!1,void 0,this)]},row.name,!0,void 0,this))},void 0,!1,void 0,this):null,jsx_dev_runtime10.jsxDEV("div",{className:"mt-1 font-mono text-[10px] text-muted-foreground/70",children:extension.importPath},void 0,!1,void 0,this)]},void 0,!0,void 0,this)}var jsx_dev_runtime11=__toESM(require_react_jsx_dev_runtime_development(),1),NON_PROP_KEYS=new Set(["data-node-path","children","className"]);function serializeProps(resolved){let out={};for(let[key,value]of Object.entries(resolved)){if(NON_PROP_KEYS.has(key))continue;out[key]=value}try{return JSON.stringify(out)}catch{return"{}"}}function LiveIslandMarker({id,extension,resolvedProps,className,...rest}){let isContentFit=extension.fit==="content",wrapperClassName=[isContentFit?"relative w-full":"relative aspect-video w-full",className].filter(Boolean).join(" ");return jsx_dev_runtime11.jsxDEV("div",{"data-live-node":"true","data-live-ref":id,"data-live-props":serializeProps(resolvedProps),"data-live-fit":isContentFit?"content":void 0,className:wrapperClassName,...rest,children:jsx_dev_runtime11.jsxDEV(ExtensionPlaceholder,{id,extension,resolvedProps,className},void 0,!1,void 0,this)},void 0,!1,void 0,this)}function providerForScreen(screen,providers,defaultProvider){if(!screen.library)return defaultProvider;return providers[screen.library]??defaultProvider}function buildExtensionRegistry(extensions){let out={};for(let[id,extension]of Object.entries(extensions)){let Component=extension.render==="live"?LiveIslandMarker:ExtensionPlaceholder;out[id]=(resolvedProps)=>import_react3.createElement(Component,{id,extension,resolvedProps,"data-node-path":resolvedProps["data-node-path"],className:typeof resolvedProps.className==="string"?resolvedProps.className:void 0})}return out}function registryForScreen(screen,providers,defaultProvider,extensions,folderCss){let provider=providerForScreen(screen,providers,defaultProvider),channel=styleChannelOf(provider,folderCss),base=provider.registryForChannel?.(channel.kind)??provider.registry;if(Object.keys(extensions).length===0)return base;return{...base,...buildExtensionRegistry(extensions)}}function renderPassForScreen(screen,providers,defaultProvider,theme,dark=!1){return providerForScreen(screen,providers,defaultProvider).renderPass?.(theme,dark)}import{readFile as readFile5}from"fs/promises";import{extname as extname2,join as join11,normalize}from"path";var STANDALONE_WARN_BYTES=8388608,ASSET_REF=/\/assets\/[A-Za-z0-9._@\-/]+/g,MIME={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".avif":"image/avif",".svg":"image/svg+xml",".ico":"image/x-icon",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf",".otf":"font/otf",".mp4":"video/mp4",".webm":"video/webm"},FONT_FETCH_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",STANDALONE_CSP="script-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'";function withStandalonePolicy(html){let meta=`<meta http-equiv="Content-Security-Policy" content="${STANDALONE_CSP}">`,head=/<head\b[^>]*>/i.exec(html);if(!head)return`${meta}${html}`;let at=head.index+head[0].length;return`${html.slice(0,at)}${meta}${html.slice(at)}`}async function inlineStandaloneDocument(html,opts){let warnings=[],out=await inlineAssets(withStandalonePolicy(html),opts.assetRoot,warnings);if(out=await inlineGoogleFonts(out,warnings),!opts.skipSizeWarning)warnings.push(...sizeWarning(out));return{html:out,warnings}}function sizeWarning(html){let bytes=Buffer.byteLength(html,"utf8");return bytes>STANDALONE_WARN_BYTES?[`the exported file is ${(bytes/1048576).toFixed(1)}MB \u2014 embedded assets add up; consider trimming large images`]:[]}async function inlineAssets(html,assetRoot,warnings){let refs=[...new Set(html.match(ASSET_REF)??[])],out=html;for(let ref of refs){let rel=normalize(ref.replace(/^\//,""));if(rel.startsWith(".."))continue;try{let bytes=await readFile5(join11(assetRoot,rel)),mime=MIME[extname2(rel).toLowerCase()]??"application/octet-stream";out=out.replaceAll(ref,`data:${mime};base64,${bytes.toString("base64")}`)}catch{warnings.push(`asset not found, left as a dead reference: ${ref}`)}}return out}async function inlineGoogleFonts(html,warnings){let linkMatch=/<link rel="stylesheet" href="(https:\/\/fonts\.googleapis\.com\/css2[^"]*)"\s*\/>/.exec(html);if(!linkMatch?.[1])return html;let cssUrl=linkMatch[1].replaceAll("&","&");try{let cssRes=await fetch(cssUrl,{headers:{"user-agent":FONT_FETCH_UA}});if(!cssRes.ok)throw Error(`fonts css fetch failed (${cssRes.status})`);let css=await cssRes.text(),fontUrls=[...new Set([...css.matchAll(/url\((https:\/\/fonts\.gstatic\.com[^)]+)\)/g)].map((m)=>m[1]))];for(let url of fontUrls){let res=await fetch(url);if(!res.ok)throw Error(`font file fetch failed (${res.status})`);let bytes=Buffer.from(await res.arrayBuffer()),mime=url.endsWith(".woff2")?"font/woff2":MIME[extname2(new URL(url).pathname)]??"font/woff2";css=css.replaceAll(url,`data:${mime};base64,${bytes.toString("base64")}`)}return html.replace(/\n[ \t]*<link rel="preconnect" href="https:\/\/fonts\.[^"]*"(?: crossorigin)? ?\/>/g,"").replace(linkMatch[0],`<style data-velloo-fonts>${css}</style>`)}catch(err){return warnings.push(`webfonts were not embedded (${err instanceof Error?err.message:String(err)}) \u2014 the file loads them from Google Fonts when opened online`),html}}function findFrame(folder,frameId){for(let board of folder.boards.values()){let frame=board.frames.find((f)=>f.id===frameId);if(frame)return{board,frame}}return null}function exportFilename(name,ext){let dashed=name.replace(/[^\w.-]+/g,"-"),start=0,end=dashed.length;while(start<end&&dashed[start]==="-")start++;while(end>start&&dashed[end-1]==="-")end--;return`${dashed.slice(start,end)||"export"}.${ext}`}async function renderExportHtml(p,screen,opts){let theme=themeByName(p.folder,opts.themeName),config=p.folder.config,canvasBundle=opts.standalone?void 0:await p.canvasBundleFor?.(screen,theme,opts.dark),baseHref=opts.standalone?void 0:p.assetOrigin?.(),liveBundleUrl=opts.standalone?void 0:p.liveBundleUrl?.(),{html}=await renderScreen(screen,theme,{viewport:opts.viewport,snapshotCss:await p.snapshotCss(),registry:registryForScreen(screen,p.providers,p.defaultProvider,config.extensions??{},config.styling?.framework),renderPass:renderPassForScreen(screen,p.providers,p.defaultProvider,theme,opts.dark),snippets:p.folder.snippets,customCss:p.folder.customCss,dark:opts.dark,...baseHref?{baseHref}:{},...liveBundleUrl?{liveBundleUrl}:{},...canvasBundle?{canvasBundle}:{},...opts.standalone?{includeRuntime:!1}:{}});return html}var themeOf=(board,override)=>override??board.theme;function screenOf(p,frame){let screen=p.folder.screens.get(frame.screen);if(!screen)throw Error(`frame "${frame.id}" points at missing screen "${frame.screen}"`);return screen}async function exportFramePng(p,board,frame,opts={}){let screen=screenOf(p,frame),viewport={w:frame.w,h:frame.h},themeName=themeOf(board,opts.theme),scale=opts.scale??1;if(opts.mode==="compare"){let[light,dark]=await Promise.all([renderExportHtml(p,screen,{dark:!1,themeName,viewport}),renderExportHtml(p,screen,{dark:!0,themeName,viewport})]);return screenshotCompareBuffer({leftHtml:light,rightHtml:dark,viewport,deviceScaleFactor:scale})}let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName,viewport}),{png}=await captureScreenshot({html,viewport,fullPage:!1,deviceScaleFactor:scale});return png}async function exportBoardPng(p,board,opts={}){let composite=await boardComposite(p,board,opts,!1),{png}=await captureScreenshot({html:composite.html,viewport:composite.viewport,fullPage:!1,deviceScaleFactor:opts.scale??1});return png}async function exportFramePdf(p,board,frame,opts={}){let screen=screenOf(p,frame),viewport={w:frame.w,h:frame.h},html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:themeOf(board,opts.theme),viewport});return pdfPageBuffer({html,viewport})}async function exportBoardPdf(p,board,opts={}){let dark=opts.mode==="dark",themeName=themeOf(board,opts.theme),pages=[];for(let frame of board.frames){let screen=p.folder.screens.get(frame.screen);if(!screen)continue;let viewport={w:frame.w,h:frame.h};pages.push({html:await renderExportHtml(p,screen,{dark,themeName,viewport}),viewport,label:frame.label??screen.name??screen.id})}if(pages.length===0)throw Error(`board "${board.id}" has no exportable frames`);return pdfDeckBuffer(pages)}async function exportScreenHtml(p,screen,opts){let html=await renderExportHtml(p,screen,{dark:opts.dark??!1,themeName:opts.theme,viewport:opts.viewport,standalone:!0});return inlineStandaloneDocument(html,{assetRoot:p.folder.root})}async function exportScreenPng(p,screen,opts){let{viewport}=opts;if(opts.mode==="compare"){let[light,dark]=await Promise.all([renderExportHtml(p,screen,{dark:!1,themeName:opts.theme,viewport}),renderExportHtml(p,screen,{dark:!0,themeName:opts.theme,viewport})]);return screenshotCompareBuffer({leftHtml:light,rightHtml:dark,viewport,deviceScaleFactor:opts.scale??1})}let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:opts.theme,viewport}),{png}=await captureScreenshot({html,viewport,fullPage:!0,deviceScaleFactor:opts.scale??1});return png}async function exportScreenPdf(p,screen,opts){let html=await renderExportHtml(p,screen,{dark:opts.mode==="dark",themeName:opts.theme,viewport:opts.viewport});return pdfPageBuffer({html,viewport:opts.viewport})}async function exportFrameHtml(p,board,frame,opts={}){let screen=screenOf(p,frame);return exportScreenHtml(p,screen,{dark:opts.mode==="dark",...themeOf(board,opts.theme)?{theme:themeOf(board,opts.theme)}:{},viewport:{w:frame.w,h:frame.h}})}async function exportBoardHtml(p,board,opts={}){let composite=await boardComposite(p,board,opts,!0);return{html:composite.html,warnings:[...composite.warnings,...sizeWarning(composite.html)]}}async function boardComposite(p,board,opts,standalone){let dark=opts.mode==="dark",themeName=themeOf(board,opts.theme),warnings=[],frames=[];for(let frame of board.frames){let screen=p.folder.screens.get(frame.screen);if(!screen)continue;let html=await renderExportHtml(p,screen,{dark,themeName,viewport:{w:frame.w,h:frame.h},standalone});if(standalone){let inlined=await inlineStandaloneDocument(html,{assetRoot:p.folder.root,skipSizeWarning:!0});html=inlined.html,warnings.push(...inlined.warnings)}frames.push({frame,html,label:frame.label??screen.name??screen.id})}if(frames.length===0)throw Error(`board "${board.id}" has no exportable frames`);let composite=buildBoardComposite(frames,{labels:!0});return{html:standalone?withStandalonePolicy(composite.html):composite.html,viewport:composite.viewport,warnings:dedupe(warnings)}}var dedupe=(list)=>[...new Set(list)];import{mkdir as mkdir7,writeFile as writeFile7}from"fs/promises";import{tmpdir}from"os";import{join as join12,resolve as resolve4}from"path";var EMPTY_MODULE=`export const components = {};
|
|
85
|
+
`;function hostAppRootFrom(folderRoot,hostApp){if(!hostApp?.root)return localDesignOf(folderRoot)?.appRoot??resolve4(folderRoot,"..");return resolveAppPath(folderRoot,hostApp.root)}function aliasPairs(hostApp){let raw=hostApp?.aliases??{"@/*":"*"};return Object.entries(raw).map(([pattern,target])=>({from:pattern.replace(/\*$/,""),to:target.replace(/\*$/,"")}))}function applyAlias(spec,aliases){for(let{from,to}of aliases)if(from&&spec.startsWith(from))return to+spec.slice(from.length);return null}function resolveImport(importPath,hostRoot,aliases){let aliased=applyAlias(importPath,aliases);return Bun.resolveSync(aliased?join12(hostRoot,aliased):importPath,hostRoot)}function aliasPlugin(hostRoot,aliases){let prefixes=aliases.map((a)=>a.from).filter(Boolean);return{name:"velloo-host-alias",setup(build){if(prefixes.length===0)return;let filter=new RegExp(`^(${prefixes.map(escapeRegExp).join("|")})`);build.onResolve({filter},(args)=>{let aliased=applyAlias(args.path,aliases);if(!aliased)return;try{return{path:Bun.resolveSync(join12(hostRoot,aliased),hostRoot)}}catch{return}})}}}function escapeRegExp(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function bundleComponents(opts){let{hostRoot,entries,aliases,minify=!1}=opts;if(entries.length===0)return{code:EMPTY_MODULE,errors:[]};let reactPath,reactDomClientPath;try{reactPath=Bun.resolveSync("react",hostRoot),reactDomClientPath=Bun.resolveSync("react-dom/client",hostRoot)}catch{return{code:EMPTY_MODULE,errors:[{message:`Live preview needs the host app's React 18+ (could not resolve \`react\`/\`react-dom/client\` from ${hostRoot}). Set config.hostApp.root if the app lives elsewhere.`}]}}let errors=[],resolved=[];for(let{id,importPath}of entries)try{resolved.push({id,path:resolveImport(importPath,hostRoot,aliases)})}catch(err){errors.push({importPath,message:`${id}: ${err instanceof Error?err.message:String(err)}`})}if(resolved.length===0)return{code:EMPTY_MODULE,errors};let entrySource=buildEntrySource(reactPath,reactDomClientPath,resolved),key=Bun.hash(opts.cacheKey??hostRoot).toString(16),dir=join12(tmpdir(),"velloo-live",key);await mkdir7(dir,{recursive:!0});let entryPath=join12(dir,"entry.tsx");await writeFile7(entryPath,entrySource,"utf8");let result;try{result=await Bun.build({entrypoints:[entryPath],target:"browser",format:"esm",minify,sourcemap:"none",define:{"process.env.NODE_ENV":'"production"'},plugins:[aliasPlugin(hostRoot,aliases)]})}catch(err){for(let e of err instanceof AggregateError?err.errors:[err])errors.push({message:e instanceof Error?e.message:String(e)});return{code:EMPTY_MODULE,errors}}if(!result.success){for(let log of result.logs)errors.push({message:typeof log==="string"?log:log.message});return{code:EMPTY_MODULE,errors}}let output=result.outputs[0];if(!output)return errors.push({message:"Bun.build produced no output artifact."}),{code:EMPTY_MODULE,errors};return{code:await output.text(),errors}}function buildEntrySource(reactPath,reactDomClientPath,components){let imports=components.map((c,i)=>`import * as __m${i} from ${JSON.stringify(c.path)};`).join(`
|
|
86
86
|
`),registry=components.map((c,i)=>` ${JSON.stringify(c.id)}: pick(__m${i}, ${JSON.stringify(c.id)}),`).join(`
|
|
87
87
|
`);return`import * as React from ${JSON.stringify(reactPath)};
|
|
88
88
|
import { createRoot } from ${JSON.stringify(reactDomClientPath)};
|
|
@@ -112,8 +112,8 @@ export const components = {
|
|
|
112
112
|
${registry}
|
|
113
113
|
};
|
|
114
114
|
export { React, createRoot, ErrorBoundary };
|
|
115
|
-
`}function registryFor(source){let{folder}=source;return(screen)=>registryForScreen(screen,source.providers,source.defaultProvider,folder.config.extensions??{},folder.config.styling?.framework)}function boardsPlacing(folder,screenId){return[...folder.boards.values()].filter((board)=>board.frames.some((frame)=>frame.screen===screenId)).map((board)=>({id:board.id,name:board.name}))}function preflightScreens(source,screens){let registry=registryFor(source),out=[];for(let screen of screens){let where={screenId:screen.id,screenName:screen.name,boards:boardsPlacing(source.folder,screen.id)};try{for(let failure of renderBodyGuarded(screen,registry(screen),source.folder.snippets).failures)out.push({...where,componentId:failure.componentId,reason:failure.reason})}catch(error){if(error instanceof RenderGuardLimitError)for(let failure of error.failures)out.push({...where,componentId:failure.componentId,reason:failure.reason});out.push({...where,componentId:null,reason:error instanceof Error?error.message:String(error)})}}return out}function screenById(folder,id){return id===void 0?void 0:folder.screens.get(id)}function screensForBoards(folder,boardIds){let seen=new Map;for(let boardId of boardIds){let board=folder.boards.get(boardId);if(!board)continue;for(let frame of board.frames){let screen=screenById(folder,frame.screen);if(screen&&!seen.has(screen.id))seen.set(screen.id,screen)}}return[...seen.values()]}function screensForExportTarget(folder,kind,id){if(kind==="board")return screensForBoards(folder,[id]);if(kind==="screen"){let screen=folder.screens.get(id);return screen?[screen]:[]}for(let board of folder.boards.values()){let frame=board.frames.find((f)=>f.id===id);if(!frame)continue;let screen=screenById(folder,frame.screen);return screen?[screen]:[]}return[]}import{existsSync as existsSync3}from"fs";import{dirname as dirname9,join as join13}from"path";import{fileURLToPath}from"url";var here=dirname9(fileURLToPath(import.meta.url));function resolveDir(){return[process.env.VELLOO_HELPERS_SRC,join13(here,"pkgs","helpers","src"),here].filter((p)=>Boolean(p)).find((d)=>existsSync3(join13(d,"heading.tsx")))??here}var helpersComponentsDir=resolveDir(),HELPER_SOURCE_FILES={Box:"box.tsx",Divider:"divider.tsx",Gradient:"gradient.tsx",Heading:"heading.tsx",Icon:"icon.tsx",Image:"image.tsx",Layer:"layer.tsx",Placeholder:"placeholder.tsx",Prose:"prose.tsx",SVG:"svg.tsx",Text:"text.tsx"};function helperSourcePath(id){let file=HELPER_SOURCE_FILES[id];return file?join13(helpersComponentsDir,file):void 0}function jsonResult(value){return{content:[{type:"text",text:JSON.stringify(value)}]}}function structuredResult(value,...extra){return{content:[{type:"text",text:JSON.stringify(value)},...extra],structuredContent:value}}function errorResult(error){return{isError:!0,content:[{type:"text",text:JSON.stringify(typeof error==="string"?{kind:"Error",message:error}:error)}]}}function toMcp(result){return result.ok?jsonResult(result.value):errorResult(result.error)}var MCP_SURFACE_MODES=["guided","full"],DEFAULT_MCP_SURFACE={mode:"guided"},LEGACY_SURFACE_ALIASES={profile:"full"};function parseMcpSurfaceSelection(modeValue){let raw=modeValue||DEFAULT_MCP_SURFACE.mode,mode=LEGACY_SURFACE_ALIASES[raw]??raw;if(!MCP_SURFACE_MODES.includes(mode))return{ok:!1,error:`unknown MCP surface "${raw}"; expected ${MCP_SURFACE_MODES.join(", ")}`};return{ok:!0,selection:{mode}}}function parseMcpSurfaceUrl(url){let parsed=new URL(url??"/mcp","http://velloo.local");return parseMcpSurfaceSelection(parsed.searchParams.get("surface"))}function withMcpSurfaceUrl(url,selection){let parsed=new URL(url);return parsed.searchParams.set("surface",selection.mode),parsed.searchParams.delete("profile"),parsed.toString()}function schemaJson(tool){if(!tool.inputSchema)return{type:"object",properties:{},additionalProperties:!1};try{return toJSONSchema(tool.inputSchema,{io:"input"})}catch{return{type:"object",description:"Schema is not representable as JSON Schema"}}}function operationHelp(operation,tool){return{operation,description:tool.description??"",inputSchema:schemaJson(tool),annotations:tool.annotations??{}}}function appendSchemaHelp(result,operation,tool){if(result.isError!==!0)return result;return{...result,content:[...result.content,{type:"text",text:JSON.stringify({kind:"OperationSchemaHelp",...operationHelp(operation,tool)})}]}}function applyMcpToolSurface(mcp,selection){let original=mcp.registerTool.bind(mcp),native=new Map,registeringNative=!0,patched=(name,config,cb)=>{let registered=original(name,config,cb);if(registeringNative){if(native.set(name,registered),selection.mode!=="full")registered.disable()}return registered};mcp.registerTool=patched;let invoke=async(operation,args,extra)=>{let tool=native.get(operation);if(!tool)return errorResult({kind:"UnknownOperation",operation});if(tool.inputSchema){let parsed=await tool.inputSchema.safeParseAsync(args);if(!parsed.success)return errorResult({kind:"InvalidOperationArguments",operation,issues:parsed.error.issues,...operationHelp(operation,tool)});return appendSchemaHelp(await tool.handler(parsed.data,extra),operation,tool)}return appendSchemaHelp(await tool.handler(extra,void 0),operation,tool)};return{nativeTools:()=>native,finish(){if(registeringNative=!1,selection.mode!=="guided")return;let operation=_enum([...native.keys()]);mcp.registerTool("call_velloo",{description:"Call one native Velloo operation. Failed calls include the exact correction schema.",inputSchema:{operation,arguments:record(string(),unknown()).describe("Arguments object for the native operation")}},async({operation:name,arguments:args},extra)=>invoke(name,args,extra)),mcp.registerTool("run_velloo_plan",{description:"Run up to eight native Velloo calls sequentially, stopping on the first error by default.",inputSchema:{calls:array(object({operation,arguments:record(string(),unknown())})).min(1).max(8),stopOnError:boolean().default(!0)}},async({calls,stopOnError},extra)=>{let content=[],failedAt=null;for(let[index,call]of calls.entries()){let result=await invoke(call.operation,call.arguments,extra);if(content.push({type:"text",text:JSON.stringify({index,operation:call.operation,isError:result.isError===!0})}),content.push(...result.content),result.isError===!0&&stopOnError!==!1){failedAt=index;break}}let summary=failedAt===null?{kind:"PlanCompleted",completed:calls.length}:{kind:"PlanFailed",failedAt,completed:failedAt+1};return{...failedAt===null?{}:{isError:!0},content:[{type:"text",text:JSON.stringify(summary)},...content]}}),mcp.registerTool("operation_schema",{description:"Return the exact schema and description for one native operation.",inputSchema:{operation}},async({operation:name})=>{let tool=native.get(name);return tool?jsonResult(operationHelp(name,tool)):errorResult({kind:"UnknownOperation",operation:name})})}}}import{mkdir as mkdir8,readFile as readFile6}from"fs/promises";import{homedir as homedir3}from"os";import{dirname as dirname10,join as join14}from"path";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(message){this.toString=function(){return"CORRUPT: "+this.message},this.message=message},invalid:function(message){this.toString=function(){return"INVALID: "+this.message},this.message=message},bug:function(message){this.toString=function(){return"BUG: "+this.message},this.message=message},notReady:function(message){this.toString=function(){return"NOT READY: "+this.message},this.message=message}}};sjcl.cipher.aes=function(key){if(!this._tables[0][0][0])this._precompute();var i,j,tmp,encKey,decKey,sbox=this._tables[0][4],decTable=this._tables[1],keyLen=key.length,rcon=1;if(keyLen!==4&&keyLen!==6&&keyLen!==8)throw new sjcl.exception.invalid("invalid aes key size");this._key=[encKey=key.slice(0),decKey=[]];for(i=keyLen;i<4*keyLen+28;i++){if(tmp=encKey[i-1],i%keyLen===0||keyLen===8&&i%keyLen===4){if(tmp=sbox[tmp>>>24]<<24^sbox[tmp>>16&255]<<16^sbox[tmp>>8&255]<<8^sbox[tmp&255],i%keyLen===0)tmp=tmp<<8^tmp>>>24^rcon<<24,rcon=rcon<<1^(rcon>>7)*283}encKey[i]=encKey[i-keyLen]^tmp}for(j=0;i;j++,i--)if(tmp=encKey[j&3?i:i-4],i<=4||j<4)decKey[j]=tmp;else decKey[j]=decTable[0][sbox[tmp>>>24]]^decTable[1][sbox[tmp>>16&255]]^decTable[2][sbox[tmp>>8&255]]^decTable[3][sbox[tmp&255]]};sjcl.cipher.aes.prototype={encrypt:function(data){return this._crypt(data,0)},decrypt:function(data){return this._crypt(data,1)},_tables:[[[],[],[],[],[]],[[],[],[],[],[]]],_precompute:function(){var encTable=this._tables[0],decTable=this._tables[1],sbox=encTable[4],sboxInv=decTable[4],i,x,xInv,d=[],th=[],x2,x4,x8,s,tEnc,tDec;for(i=0;i<256;i++)th[(d[i]=i<<1^(i>>7)*283)^i]=i;for(x=xInv=0;!sbox[x];x^=x2||1,xInv=th[xInv]||1){s=xInv^xInv<<1^xInv<<2^xInv<<3^xInv<<4,s=s>>8^s&255^99,sbox[x]=s,sboxInv[s]=x,x8=d[x4=d[x2=d[x]]],tDec=x8*16843009^x4*65537^x2*257^x*16843008,tEnc=d[s]*257^s*16843008;for(i=0;i<4;i++)encTable[i][x]=tEnc=tEnc<<24^tEnc>>>8,decTable[i][s]=tDec=tDec<<24^tDec>>>8}for(i=0;i<5;i++)encTable[i]=encTable[i].slice(0),decTable[i]=decTable[i].slice(0)},_crypt:function(input,dir){if(input.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var key=this._key[dir],a=input[0]^key[0],b=input[dir?3:1]^key[1],c=input[2]^key[2],d=input[dir?1:3]^key[3],a2,b2,c2,nInnerRounds=key.length/4-2,i,kIndex=4,out=[0,0,0,0],table=this._tables[dir],t0=table[0],t1=table[1],t2=table[2],t3=table[3],sbox=table[4];for(i=0;i<nInnerRounds;i++)a2=t0[a>>>24]^t1[b>>16&255]^t2[c>>8&255]^t3[d&255]^key[kIndex],b2=t0[b>>>24]^t1[c>>16&255]^t2[d>>8&255]^t3[a&255]^key[kIndex+1],c2=t0[c>>>24]^t1[d>>16&255]^t2[a>>8&255]^t3[b&255]^key[kIndex+2],d=t0[d>>>24]^t1[a>>16&255]^t2[b>>8&255]^t3[c&255]^key[kIndex+3],kIndex+=4,a=a2,b=b2,c=c2;for(i=0;i<4;i++)out[dir?3&-i:i]=sbox[a>>>24]<<24^sbox[b>>16&255]<<16^sbox[c>>8&255]<<8^sbox[d&255]^key[kIndex++],a2=a,a=b,b=c,c=d,d=a2;return out}};sjcl.bitArray={bitSlice:function(a,bstart,bend){return a=sjcl.bitArray._shiftRight(a.slice(bstart/32),32-(bstart&31)).slice(1),bend===void 0?a:sjcl.bitArray.clamp(a,bend-bstart)},extract:function(a,bstart,blength){var x,sh=Math.floor(-bstart-blength&31);if((bstart+blength-1^bstart)&-32)x=a[bstart/32|0]<<32-sh^a[bstart/32+1|0]>>>sh;else x=a[bstart/32|0]>>>sh;return x&(1<<blength)-1},concat:function(a1,a2){if(a1.length===0||a2.length===0)return a1.concat(a2);var last=a1[a1.length-1],shift=sjcl.bitArray.getPartial(last);if(shift===32)return a1.concat(a2);else return sjcl.bitArray._shiftRight(a2,shift,last|0,a1.slice(0,a1.length-1))},bitLength:function(a){var l=a.length,x;if(l===0)return 0;return x=a[l-1],(l-1)*32+sjcl.bitArray.getPartial(x)},clamp:function(a,len){if(a.length*32<len)return a;a=a.slice(0,Math.ceil(len/32));var l=a.length;if(len=len&31,l>0&&len)a[l-1]=sjcl.bitArray.partial(len,a[l-1]&2147483648>>len-1,1);return a},partial:function(len,x,_end){if(len===32)return x;return(_end?x|0:x<<32-len)+len*1099511627776},getPartial:function(x){return Math.round(x/1099511627776)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var x=0,i;for(i=0;i<a.length;i++)x|=a[i]^b[i];return x===0},_shiftRight:function(a,shift,carry,out){var i,last2=0,shift2;if(out===void 0)out=[];for(;shift>=32;shift-=32)out.push(carry),carry=0;if(shift===0)return out.concat(a);for(i=0;i<a.length;i++)out.push(carry|a[i]>>>shift),carry=a[i]<<32-shift;return last2=a.length?a[a.length-1]:0,shift2=sjcl.bitArray.getPartial(last2),out.push(sjcl.bitArray.partial(shift+shift2&31,shift+shift2>32?carry:out.pop(),1)),out},_xor4:function(x,y){return[x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]},byteswapM:function(a){var i,v,m=65280;for(i=0;i<a.length;++i)v=a[i],a[i]=v>>>24|v>>>8&m|(v&m)<<8|v<<24;return a}};sjcl.codec.utf8String={fromBits:function(arr){var out="",bl=sjcl.bitArray.bitLength(arr),i,tmp;for(i=0;i<bl/8;i++){if((i&3)===0)tmp=arr[i/4];out+=String.fromCharCode(tmp>>>8>>>8>>>8),tmp<<=8}return decodeURIComponent(escape(out))},toBits:function(str){str=unescape(encodeURIComponent(str));var out=[],i,tmp=0;for(i=0;i<str.length;i++)if(tmp=tmp<<8|str.charCodeAt(i),(i&3)===3)out.push(tmp),tmp=0;if(i&3)out.push(sjcl.bitArray.partial(8*(i&3),tmp));return out}};sjcl.codec.hex={fromBits:function(arr){var out="",i;for(i=0;i<arr.length;i++)out+=((arr[i]|0)+263882790666240).toString(16).substr(4);return out.substr(0,sjcl.bitArray.bitLength(arr)/4)},toBits:function(str){var i,out=[],len;str=str.replace(/\s|0x/g,""),len=str.length,str=str+"00000000";for(i=0;i<str.length;i+=8)out.push(parseInt(str.substr(i,8),16)^0);return sjcl.bitArray.clamp(out,len*4)}};sjcl.codec.base64={_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(arr,_noEquals,_url){var out="",i,bits=0,c=sjcl.codec.base64._chars,ta=0,bl=sjcl.bitArray.bitLength(arr);if(_url)c=c.substr(0,62)+"-_";for(i=0;out.length*6<bl;)if(out+=c.charAt((ta^arr[i]>>>bits)>>>26),bits<6)ta=arr[i]<<6-bits,bits+=26,i++;else ta<<=6,bits-=6;while(out.length&3&&!_noEquals)out+="=";return out},toBits:function(str,_url){str=str.replace(/\s|=/g,"");var out=[],i,bits=0,c=sjcl.codec.base64._chars,ta=0,x;if(_url)c=c.substr(0,62)+"-_";for(i=0;i<str.length;i++){if(x=c.indexOf(str.charAt(i)),x<0)throw new sjcl.exception.invalid("this isn't base64!");if(bits>26)bits-=26,out.push(ta^x>>>bits),ta=x<<32-bits;else bits+=6,ta^=x<<32-bits}if(bits&56)out.push(sjcl.bitArray.partial(bits&56,ta,1));return out}};sjcl.codec.base64url={fromBits:function(arr){return sjcl.codec.base64.fromBits(arr,1,1)},toBits:function(str){return sjcl.codec.base64.toBits(str,1)}};sjcl.codec.bytes={fromBits:function(arr){var out=[],bl=sjcl.bitArray.bitLength(arr),i,tmp;for(i=0;i<bl/8;i++){if((i&3)===0)tmp=arr[i/4];out.push(tmp>>>24),tmp<<=8}return out},toBits:function(bytes){var out=[],i,tmp=0;for(i=0;i<bytes.length;i++)if(tmp=tmp<<8|bytes[i],(i&3)===3)out.push(tmp),tmp=0;if(i&3)out.push(sjcl.bitArray.partial(8*(i&3),tmp));return out}};sjcl.hash.sha256=function(hash){if(!this._key[0])this._precompute();if(hash)this._h=hash._h.slice(0),this._buffer=hash._buffer.slice(0),this._length=hash._length;else this.reset()};sjcl.hash.sha256.hash=function(data){return new sjcl.hash.sha256().update(data).finalize()};sjcl.hash.sha256.prototype={blockSize:512,reset:function(){return this._h=this._init.slice(0),this._buffer=[],this._length=0,this},update:function(data){if(typeof data==="string")data=sjcl.codec.utf8String.toBits(data);var i,b=this._buffer=sjcl.bitArray.concat(this._buffer,data),ol=this._length,nl=this._length=ol+sjcl.bitArray.bitLength(data);if(nl>9007199254740991)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if(typeof Uint32Array<"u"){var c=new Uint32Array(b),j=0;for(i=512+ol-(512+ol&511);i<=nl;i+=512)this._block(c.subarray(16*j,16*(j+1))),j+=1;b.splice(0,16*j)}else for(i=512+ol-(512+ol&511);i<=nl;i+=512)this._block(b.splice(0,16));return this},finalize:function(){var i,b=this._buffer,h=this._h;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(i=b.length+2;i&15;i++)b.push(0);b.push(Math.floor(this._length/4294967296)),b.push(this._length|0);while(b.length)this._block(b.splice(0,16));return this.reset(),h},_init:[],_key:[],_precompute:function(){var i=0,prime=2,factor,isPrime;function frac(x){return(x-Math.floor(x))*4294967296|0}for(;i<64;prime++){isPrime=!0;for(factor=2;factor*factor<=prime;factor++)if(prime%factor===0){isPrime=!1;break}if(isPrime){if(i<8)this._init[i]=frac(Math.pow(prime,0.5));this._key[i]=frac(Math.pow(prime,0.3333333333333333)),i++}}},_block:function(w){var i,tmp,a,b,h=this._h,k=this._key,h0=h[0],h1=h[1],h2=h[2],h3=h[3],h4=h[4],h5=h[5],h6=h[6],h7=h[7];for(i=0;i<64;i++){if(i<16)tmp=w[i];else a=w[i+1&15],b=w[i+14&15],tmp=w[i&15]=(a>>>7^a>>>18^a>>>3^a<<25^a<<14)+(b>>>17^b>>>19^b>>>10^b<<15^b<<13)+w[i&15]+w[i+9&15]|0;tmp=tmp+h7+(h4>>>6^h4>>>11^h4>>>25^h4<<26^h4<<21^h4<<7)+(h6^h4&(h5^h6))+k[i],h7=h6,h6=h5,h5=h4,h4=h3+tmp|0,h3=h2,h2=h1,h1=h0,h0=tmp+(h1&h2^h3&(h1^h2))+(h1>>>2^h1>>>13^h1>>>22^h1<<30^h1<<19^h1<<10)|0}h[0]=h[0]+h0|0,h[1]=h[1]+h1|0,h[2]=h[2]+h2|0,h[3]=h[3]+h3|0,h[4]=h[4]+h4|0,h[5]=h[5]+h5|0,h[6]=h[6]+h6|0,h[7]=h[7]+h7|0}};sjcl.mode.ccm={name:"ccm",_progressListeners:[],listenProgress:function(cb){sjcl.mode.ccm._progressListeners.push(cb)},unListenProgress:function(cb){var index=sjcl.mode.ccm._progressListeners.indexOf(cb);if(index>-1)sjcl.mode.ccm._progressListeners.splice(index,1)},_callProgressListener:function(val){var p=sjcl.mode.ccm._progressListeners.slice(),i;for(i=0;i<p.length;i+=1)p[i](val)},encrypt:function(prf,plaintext,iv,adata,tlen){var L,out=plaintext.slice(0),tag,w=sjcl.bitArray,ivl=w.bitLength(iv)/8,ol=w.bitLength(out)/8;if(tlen=tlen||64,adata=adata||[],ivl<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(L=2;L<4&&ol>>>8*L;L++);if(L<15-ivl)L=15-ivl;return iv=w.clamp(iv,8*(15-L)),tag=sjcl.mode.ccm._computeTag(prf,plaintext,iv,adata,tlen,L),out=sjcl.mode.ccm._ctrMode(prf,out,iv,tag,tlen,L),w.concat(out.data,out.tag)},decrypt:function(prf,ciphertext,iv,adata,tlen){tlen=tlen||64,adata=adata||[];var L,w=sjcl.bitArray,ivl=w.bitLength(iv)/8,ol=w.bitLength(ciphertext),out=w.clamp(ciphertext,ol-tlen),tag=w.bitSlice(ciphertext,ol-tlen),tag2;if(ol=(ol-tlen)/8,ivl<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(L=2;L<4&&ol>>>8*L;L++);if(L<15-ivl)L=15-ivl;if(iv=w.clamp(iv,8*(15-L)),out=sjcl.mode.ccm._ctrMode(prf,out,iv,tag,tlen,L),tag2=sjcl.mode.ccm._computeTag(prf,out.data,iv,adata,tlen,L),!w.equal(out.tag,tag2))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return out.data},_macAdditionalData:function(prf,adata,iv,tlen,ol,L){var mac,tmp,i,macData=[],w=sjcl.bitArray,xor=w._xor4;if(mac=[w.partial(8,(adata.length?64:0)|tlen-2<<2|L-1)],mac=w.concat(mac,iv),mac[3]|=ol,mac=prf.encrypt(mac),adata.length){if(tmp=w.bitLength(adata)/8,tmp<=65279)macData=[w.partial(16,tmp)];else if(tmp<=4294967295)macData=w.concat([w.partial(16,65534)],[tmp]);macData=w.concat(macData,adata);for(i=0;i<macData.length;i+=4)mac=prf.encrypt(xor(mac,macData.slice(i,i+4).concat([0,0,0])))}return mac},_computeTag:function(prf,plaintext,iv,adata,tlen,L){var mac,i,w=sjcl.bitArray,xor=w._xor4;if(tlen/=8,tlen%2||tlen<4||tlen>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(adata.length>4294967295||plaintext.length>4294967295)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");mac=sjcl.mode.ccm._macAdditionalData(prf,adata,iv,tlen,w.bitLength(plaintext)/8,L);for(i=0;i<plaintext.length;i+=4)mac=prf.encrypt(xor(mac,plaintext.slice(i,i+4).concat([0,0,0])));return w.clamp(mac,tlen*8)},_ctrMode:function(prf,data,iv,tag,tlen,L){var enc,i,w=sjcl.bitArray,xor=w._xor4,ctr,l=data.length,bl=w.bitLength(data),n=l/50,p=n;if(ctr=w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4),tag=w.bitSlice(xor(tag,prf.encrypt(ctr)),0,tlen),!l)return{tag,data:[]};for(i=0;i<l;i+=4){if(i>n)sjcl.mode.ccm._callProgressListener(i/l),n+=p;ctr[3]++,enc=prf.encrypt(ctr),data[i]^=enc[0],data[i+1]^=enc[1],data[i+2]^=enc[2],data[i+3]^=enc[3]}return{tag,data:w.clamp(data,bl)}}};sjcl.misc.hmac=function(key,Hash){this._hash=Hash=Hash||sjcl.hash.sha256;var exKey=[[],[]],i,bs=Hash.prototype.blockSize/32;if(this._baseHash=[new Hash,new Hash],key.length>bs)key=Hash.hash(key);for(i=0;i<bs;i++)exKey[0][i]=key[i]^909522486,exKey[1][i]=key[i]^1549556828;this._baseHash[0].update(exKey[0]),this._baseHash[1].update(exKey[1]),this._resultHash=new Hash(this._baseHash[0])};sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(data){if(!this._updated)return this.update(data),this.digest(data);else throw new sjcl.exception.invalid("encrypt on already updated hmac called!")};sjcl.misc.hmac.prototype.reset=function(){this._resultHash=new this._hash(this._baseHash[0]),this._updated=!1};sjcl.misc.hmac.prototype.update=function(data){this._updated=!0,this._resultHash.update(data)};sjcl.misc.hmac.prototype.digest=function(){var w=this._resultHash.finalize(),result=new this._hash(this._baseHash[1]).update(w).finalize();return this.reset(),result};sjcl.misc.pbkdf2=function(password,salt,count,length,Prff){if(count=count||1e4,length<0||count<0)throw new sjcl.exception.invalid("invalid params to pbkdf2");if(typeof password==="string")password=sjcl.codec.utf8String.toBits(password);if(typeof salt==="string")salt=sjcl.codec.utf8String.toBits(salt);Prff=Prff||sjcl.misc.hmac;var prf=new Prff(password),u,ui,i,j,k,out=[],b=sjcl.bitArray;for(k=1;32*out.length<(length||1);k++){u=ui=prf.encrypt(b.concat(salt,[k]));for(i=1;i<count;i++){ui=prf.encrypt(ui);for(j=0;j<ui.length;j++)u[j]^=ui[j]}out=out.concat(u)}if(length)out=b.clamp(out,length);return out};sjcl.prng=function(defaultParanoia){this._pools=[new sjcl.hash.sha256],this._poolEntropy=[0],this._reseedCount=0,this._robins={},this._eventId=0,this._collectorIds={},this._collectorIdNext=0,this._strength=0,this._poolStrength=0,this._nextReseed=0,this._key=[0,0,0,0,0,0,0,0],this._counter=[0,0,0,0],this._cipher=void 0,this._defaultParanoia=defaultParanoia,this._collectorsStarted=!1,this._callbacks={progress:{},seeded:{}},this._callbackI=0,this._NOT_READY=0,this._READY=1,this._REQUIRES_RESEED=2,this._MAX_WORDS_PER_BURST=65536,this._PARANOIA_LEVELS=[0,48,64,96,128,192,256,384,512,768,1024],this._MILLISECONDS_PER_RESEED=30000,this._BITS_PER_RESEED=80};sjcl.prng.prototype={randomWords:function(nwords,paranoia){var out=[],i,readiness=this.isReady(paranoia),g;if(readiness===this._NOT_READY)throw new sjcl.exception.notReady("generator isn't seeded");else if(readiness&this._REQUIRES_RESEED)this._reseedFromPools(!(readiness&this._READY));for(i=0;i<nwords;i+=4){if((i+1)%this._MAX_WORDS_PER_BURST===0)this._gate();g=this._gen4words(),out.push(g[0],g[1],g[2],g[3])}return this._gate(),out.slice(0,nwords)},setDefaultParanoia:function(paranoia,allowZeroParanoia){if(paranoia===0&&allowZeroParanoia!=="Setting paranoia=0 will ruin your security; use it only for testing")throw new sjcl.exception.invalid("Setting paranoia=0 will ruin your security; use it only for testing");this._defaultParanoia=paranoia},addEntropy:function(data,estimatedEntropy,source){source=source||"user";var id,i,tmp,t=new Date().valueOf(),robin=this._robins[source],oldReady=this.isReady(),err=0,objName;if(id=this._collectorIds[source],id===void 0)id=this._collectorIds[source]=this._collectorIdNext++;if(robin===void 0)robin=this._robins[source]=0;switch(this._robins[source]=(this._robins[source]+1)%this._pools.length,typeof data){case"number":if(estimatedEntropy===void 0)estimatedEntropy=1;this._pools[robin].update([id,this._eventId++,1,estimatedEntropy,t,1,data|0]);break;case"object":if(objName=Object.prototype.toString.call(data),objName==="[object Uint32Array]"){tmp=[];for(i=0;i<data.length;i++)tmp.push(data[i]);data=tmp}else{if(objName!=="[object Array]")err=1;for(i=0;i<data.length&&!err;i++)if(typeof data[i]!=="number")err=1}if(!err){if(estimatedEntropy===void 0){estimatedEntropy=0;for(i=0;i<data.length;i++){tmp=data[i];while(tmp>0)estimatedEntropy++,tmp=tmp>>>1}}this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data))}break;case"string":if(estimatedEntropy===void 0)estimatedEntropy=data.length;this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]),this._pools[robin].update(data);break;default:err=1}if(err)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");if(this._poolEntropy[robin]+=estimatedEntropy,this._poolStrength+=estimatedEntropy,oldReady===this._NOT_READY){if(this.isReady()!==this._NOT_READY)this._fireEvent("seeded",Math.max(this._strength,this._poolStrength));this._fireEvent("progress",this.getProgress())}},isReady:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia!==void 0?paranoia:this._defaultParanoia];if(this._strength&&this._strength>=entropyRequired)return this._poolEntropy[0]>this._BITS_PER_RESEED&&new Date().valueOf()>this._nextReseed?this._REQUIRES_RESEED|this._READY:this._READY;else return this._poolStrength>=entropyRequired?this._REQUIRES_RESEED|this._NOT_READY:this._NOT_READY},getProgress:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia?paranoia:this._defaultParanoia];if(this._strength>=entropyRequired)return 1;else return this._poolStrength>entropyRequired?1:this._poolStrength/entropyRequired},startCollectors:function(){if(this._collectorsStarted)return;if(this._eventListener={loadTimeCollector:this._bind(this._loadTimeCollector),mouseCollector:this._bind(this._mouseCollector),keyboardCollector:this._bind(this._keyboardCollector),accelerometerCollector:this._bind(this._accelerometerCollector),touchCollector:this._bind(this._touchCollector)},window.addEventListener)window.addEventListener("load",this._eventListener.loadTimeCollector,!1),window.addEventListener("mousemove",this._eventListener.mouseCollector,!1),window.addEventListener("keypress",this._eventListener.keyboardCollector,!1),window.addEventListener("devicemotion",this._eventListener.accelerometerCollector,!1),window.addEventListener("touchmove",this._eventListener.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this._eventListener.loadTimeCollector),document.attachEvent("onmousemove",this._eventListener.mouseCollector),document.attachEvent("keypress",this._eventListener.keyboardCollector);else throw new sjcl.exception.bug("can't attach event");this._collectorsStarted=!0},stopCollectors:function(){if(!this._collectorsStarted)return;if(window.removeEventListener)window.removeEventListener("load",this._eventListener.loadTimeCollector,!1),window.removeEventListener("mousemove",this._eventListener.mouseCollector,!1),window.removeEventListener("keypress",this._eventListener.keyboardCollector,!1),window.removeEventListener("devicemotion",this._eventListener.accelerometerCollector,!1),window.removeEventListener("touchmove",this._eventListener.touchCollector,!1);else if(document.detachEvent)document.detachEvent("onload",this._eventListener.loadTimeCollector),document.detachEvent("onmousemove",this._eventListener.mouseCollector),document.detachEvent("keypress",this._eventListener.keyboardCollector);this._collectorsStarted=!1},addEventListener:function(name,callback){this._callbacks[name][this._callbackI++]=callback},removeEventListener:function(name,cb){var i,j,cbs=this._callbacks[name],jsTemp=[];for(j in cbs)if(cbs.hasOwnProperty(j)&&cbs[j]===cb)jsTemp.push(j);for(i=0;i<jsTemp.length;i++)j=jsTemp[i],delete cbs[j]},_bind:function(func){var that=this;return function(){func.apply(that,arguments)}},_gen4words:function(){for(var i=0;i<4;i++)if(this._counter[i]=this._counter[i]+1|0,this._counter[i])break;return this._cipher.encrypt(this._counter)},_gate:function(){this._key=this._gen4words().concat(this._gen4words()),this._cipher=new sjcl.cipher.aes(this._key)},_reseed:function(seedWords){this._key=sjcl.hash.sha256.hash(this._key.concat(seedWords)),this._cipher=new sjcl.cipher.aes(this._key);for(var i=0;i<4;i++)if(this._counter[i]=this._counter[i]+1|0,this._counter[i])break},_reseedFromPools:function(full){var reseedData=[],strength=0,i;this._nextReseed=reseedData[0]=new Date().valueOf()+this._MILLISECONDS_PER_RESEED;for(i=0;i<16;i++)reseedData.push(Math.random()*4294967296|0);for(i=0;i<this._pools.length;i++)if(reseedData=reseedData.concat(this._pools[i].finalize()),strength+=this._poolEntropy[i],this._poolEntropy[i]=0,!full&&this._reseedCount&1<<i)break;if(this._reseedCount>=1<<this._pools.length)this._pools.push(new sjcl.hash.sha256),this._poolEntropy.push(0);if(this._poolStrength-=strength,strength>this._strength)this._strength=strength;this._reseedCount++,this._reseed(reseedData)},_keyboardCollector:function(){this._addCurrentTimeToEntropy(1)},_mouseCollector:function(ev){var x,y;try{x=ev.x||ev.clientX||ev.offsetX||0,y=ev.y||ev.clientY||ev.offsetY||0}catch(err){x=0,y=0}if(x!=0&&y!=0)this.addEntropy([x,y],2,"mouse");this._addCurrentTimeToEntropy(0)},_touchCollector:function(ev){var touch=ev.touches[0]||ev.changedTouches[0],x=touch.pageX||touch.clientX,y=touch.pageY||touch.clientY;this.addEntropy([x,y],1,"touch"),this._addCurrentTimeToEntropy(0)},_loadTimeCollector:function(){this._addCurrentTimeToEntropy(2)},_addCurrentTimeToEntropy:function(estimatedEntropy){if(typeof window<"u"&&window.performance&&typeof window.performance.now==="function")this.addEntropy(window.performance.now(),estimatedEntropy,"loadtime");else this.addEntropy(new Date().valueOf(),estimatedEntropy,"loadtime")},_accelerometerCollector:function(ev){var ac=ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z;if(window.orientation){var or=window.orientation;if(typeof or==="number")this.addEntropy(or,1,"accelerometer")}if(ac)this.addEntropy(ac,2,"accelerometer");this._addCurrentTimeToEntropy(0)},_fireEvent:function(name,arg){var j,cbs=sjcl.random._callbacks[name],cbsTemp=[];for(j in cbs)if(cbs.hasOwnProperty(j))cbsTemp.push(cbs[j]);for(j=0;j<cbsTemp.length;j++)cbsTemp[j](arg)}};sjcl.random=new sjcl.prng(6);(function(){function getCryptoModule(){try{return __require("crypto")}catch(e){return null}}try{var buf,crypt,ab;if(typeof module_sjcl<"u"&&exports_sjcl&&(crypt=getCryptoModule())&&crypt.randomBytes)buf=crypt.randomBytes(128),buf=new Uint32Array(new Uint8Array(buf).buffer),sjcl.random.addEntropy(buf,1024,"crypto.randomBytes");else if(typeof window<"u"&&typeof Uint32Array<"u"){if(ab=new Uint32Array(32),window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(ab);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(ab);else return;sjcl.random.addEntropy(ab,1024,"crypto.getRandomValues")}}catch(e){if(typeof window<"u"&&window.console)console.log("There was an error collecting entropy from the browser:"),console.log(e)}})();sjcl.json={defaults:{v:1,iter:1e4,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},_encrypt:function(password,plaintext,params,rp){params=params||{},rp=rp||{};var j=sjcl.json,p=j._add({iv:sjcl.random.randomWords(4,0)},j.defaults),tmp,prp,adata;if(j._add(p,params),adata=p.adata,typeof p.salt==="string")p.salt=sjcl.codec.base64.toBits(p.salt);if(typeof p.iv==="string")p.iv=sjcl.codec.base64.toBits(p.iv);if(!sjcl.mode[p.mode]||!sjcl.cipher[p.cipher]||typeof password==="string"&&p.iter<=100||p.ts!==64&&p.ts!==96&&p.ts!==128||p.ks!==128&&p.ks!==192&&p.ks!==256||(p.iv.length<2||p.iv.length>4))throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof password==="string")tmp=sjcl.misc.cachedPbkdf2(password,p),password=tmp.key.slice(0,p.ks/32),p.salt=tmp.salt;else if(sjcl.ecc&&password instanceof sjcl.ecc.elGamal.publicKey)tmp=password.kem(),p.kemtag=tmp.tag,password=tmp.key.slice(0,p.ks/32);if(typeof plaintext==="string")plaintext=sjcl.codec.utf8String.toBits(plaintext);if(typeof adata==="string")p.adata=adata=sjcl.codec.utf8String.toBits(adata);if(prp=new sjcl.cipher[p.cipher](password),j._add(rp,p),rp.key=password,p.mode==="ccm"&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&plaintext instanceof ArrayBuffer)p.ct=sjcl.arrayBuffer.ccm.encrypt(prp,plaintext,p.iv,adata,p.ts);else p.ct=sjcl.mode[p.mode].encrypt(prp,plaintext,p.iv,adata,p.ts);return p},encrypt:function(password,plaintext,params,rp){var j=sjcl.json,p=j._encrypt.apply(j,arguments);return j.encode(p)},_decrypt:function(password,ciphertext,params,rp){params=params||{},rp=rp||{};var j=sjcl.json,p=j._add(j._add(j._add({},j.defaults),ciphertext),params,!0),ct,tmp,prp,adata=p.adata;if(typeof p.salt==="string")p.salt=sjcl.codec.base64.toBits(p.salt);if(typeof p.iv==="string")p.iv=sjcl.codec.base64.toBits(p.iv);if(!sjcl.mode[p.mode]||!sjcl.cipher[p.cipher]||typeof password==="string"&&p.iter<=100||p.ts!==64&&p.ts!==96&&p.ts!==128||p.ks!==128&&p.ks!==192&&p.ks!==256||!p.iv||(p.iv.length<2||p.iv.length>4))throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof password==="string")tmp=sjcl.misc.cachedPbkdf2(password,p),password=tmp.key.slice(0,p.ks/32),p.salt=tmp.salt;else if(sjcl.ecc&&password instanceof sjcl.ecc.elGamal.secretKey)password=password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32);if(typeof adata==="string")adata=sjcl.codec.utf8String.toBits(adata);if(prp=new sjcl.cipher[p.cipher](password),p.mode==="ccm"&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&p.ct instanceof ArrayBuffer)ct=sjcl.arrayBuffer.ccm.decrypt(prp,p.ct,p.iv,p.tag,adata,p.ts);else ct=sjcl.mode[p.mode].decrypt(prp,p.ct,p.iv,adata,p.ts);if(j._add(rp,p),rp.key=password,params.raw===1)return ct;else return sjcl.codec.utf8String.fromBits(ct)},decrypt:function(password,ciphertext,params,rp){var j=sjcl.json;return j._decrypt(password,j.decode(ciphertext),params,rp)},encode:function(obj){var i,out="{",comma="";for(i in obj)if(obj.hasOwnProperty(i)){if(!i.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");switch(out+=comma+'"'+i+'":',comma=",",typeof obj[i]){case"number":case"boolean":out+=obj[i];break;case"string":out+='"'+escape(obj[i])+'"';break;case"object":out+='"'+sjcl.codec.base64.fromBits(obj[i],0)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type")}}return out+"}"},decode:function(str){if(str=str.replace(/\s/g,""),!str.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");var a=str.replace(/^\{|\}$/g,"").split(/,/),out={},i,m;for(i=0;i<a.length;i++){if(!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");if(m[3]!=null)out[m[2]]=parseInt(m[3],10);else if(m[4]!=null)out[m[2]]=m[2].match(/^(ct|adata|salt|iv)$/)?sjcl.codec.base64.toBits(m[4]):unescape(m[4]);else if(m[5]!=null)out[m[2]]=m[5]==="true"}return out},_add:function(target,src,requireSame){if(target===void 0)target={};if(src===void 0)return target;var i;for(i in src)if(src.hasOwnProperty(i)){if(requireSame&&target[i]!==void 0&&target[i]!==src[i])throw new sjcl.exception.invalid("required parameter overridden");target[i]=src[i]}return target},_subtract:function(plus,minus){var out={},i;for(i in plus)if(plus.hasOwnProperty(i)&&plus[i]!==minus[i])out[i]=plus[i];return out},_filter:function(src,filter){var out={},i;for(i=0;i<filter.length;i++)if(src[filter[i]]!==void 0)out[filter[i]]=src[filter[i]];return out}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc._pbkdf2Cache={};sjcl.misc.cachedPbkdf2=function(password,obj){var cache=sjcl.misc._pbkdf2Cache,c,cp,str,salt,iter;return obj=obj||{},iter=obj.iter||1000,cp=cache[password]=cache[password]||{},c=cp[iter]=cp[iter]||{firstSalt:obj.salt&&obj.salt.length?obj.salt.slice(0):sjcl.random.randomWords(2,0)},salt=obj.salt===void 0?c.firstSalt:obj.salt,c[salt]=c[salt]||sjcl.misc.pbkdf2(password,salt,obj.iter),{key:c[salt].slice(0),salt:salt.slice(0)}};sjcl.bn=function(it){this.initWith(it)};sjcl.bn.prototype={radix:24,maxMul:8,_class:sjcl.bn,copy:function(){return new this._class(this)},initWith:function(it){var i=0,k;switch(typeof it){case"object":this.limbs=it.limbs.slice(0);break;case"number":this.limbs=[it],this.normalize();break;case"string":it=it.replace(/^0x/,""),this.limbs=[],k=this.radix/4;for(i=0;i<it.length;i+=k)this.limbs.push(parseInt(it.substring(Math.max(it.length-i-k,0),it.length-i),16));break;default:this.limbs=[0]}return this},equals:function(that){if(typeof that==="number")that=new this._class(that);var difference=0,i;this.fullReduce(),that.fullReduce();for(i=0;i<this.limbs.length||i<that.limbs.length;i++)difference|=this.getLimb(i)^that.getLimb(i);return difference===0},getLimb:function(i){return i>=this.limbs.length?0:this.limbs[i]},greaterEquals:function(that){if(typeof that==="number")that=new this._class(that);var less=0,greater=0,i,a,b;i=Math.max(this.limbs.length,that.limbs.length)-1;for(;i>=0;i--)a=this.getLimb(i),b=that.getLimb(i),greater|=b-a&~less,less|=a-b&~greater;return(greater|~less)>>>31},toString:function(){this.fullReduce();var out="",i,s,l=this.limbs;for(i=0;i<this.limbs.length;i++){s=l[i].toString(16);while(i<this.limbs.length-1&&s.length<6)s="0"+s;out=s+out}return"0x"+out},addM:function(that){if(typeof that!=="object")that=new this._class(that);var i,l=this.limbs,ll=that.limbs;for(i=l.length;i<ll.length;i++)l[i]=0;for(i=0;i<ll.length;i++)l[i]+=ll[i];return this},doubleM:function(){var i,carry=0,tmp,r=this.radix,m=this.radixMask,l=this.limbs;for(i=0;i<l.length;i++)tmp=l[i],tmp=tmp+tmp+carry,l[i]=tmp&m,carry=tmp>>r;if(carry)l.push(carry);return this},halveM:function(){var i,carry=0,tmp,r=this.radix,l=this.limbs;for(i=l.length-1;i>=0;i--)tmp=l[i],l[i]=tmp+carry>>1,carry=(tmp&1)<<r;if(!l[l.length-1])l.pop();return this},subM:function(that){if(typeof that!=="object")that=new this._class(that);var i,l=this.limbs,ll=that.limbs;for(i=l.length;i<ll.length;i++)l[i]=0;for(i=0;i<ll.length;i++)l[i]-=ll[i];return this},mod:function(that){var neg=!this.greaterEquals(new sjcl.bn(0));that=new sjcl.bn(that).normalize();var out=new sjcl.bn(this).normalize(),ci=0;if(neg)out=new sjcl.bn(0).subM(out).normalize();for(;out.greaterEquals(that);ci++)that.doubleM();if(neg)out=that.sub(out).normalize();for(;ci>0;ci--)if(that.halveM(),out.greaterEquals(that))out.subM(that).normalize();return out.trim()},inverseMod:function(p){var a=new sjcl.bn(1),b=new sjcl.bn(0),x=new sjcl.bn(this),y=new sjcl.bn(p),tmp,i,nz=1;if(!(p.limbs[0]&1))throw new sjcl.exception.invalid("inverseMod: p must be odd");do{if(x.limbs[0]&1){if(!x.greaterEquals(y))tmp=x,x=y,y=tmp,tmp=a,a=b,b=tmp;if(x.subM(y),x.normalize(),!a.greaterEquals(b))a.addM(p);a.subM(b)}if(x.halveM(),a.limbs[0]&1)a.addM(p);a.normalize(),a.halveM();for(i=nz=0;i<x.limbs.length;i++)nz|=x.limbs[i]}while(nz);if(!y.equals(1))throw new sjcl.exception.invalid("inverseMod: p and x must be relatively prime");return b},add:function(that){return this.copy().addM(that)},sub:function(that){return this.copy().subM(that)},mul:function(that){if(typeof that==="number")that=new this._class(that);else that.normalize();this.normalize();var i,j,a=this.limbs,b=that.limbs,al=a.length,bl=b.length,out=new this._class,c=out.limbs,ai,ii=this.maxMul;for(i=0;i<this.limbs.length+that.limbs.length+1;i++)c[i]=0;for(i=0;i<al;i++){ai=a[i];for(j=0;j<bl;j++)c[i+j]+=ai*b[j];if(!--ii)ii=this.maxMul,out.cnormalize()}return out.cnormalize().reduce()},square:function(){return this.mul(this)},power:function(l){l=new sjcl.bn(l).normalize().trim().limbs;var i,j,out=new this._class(1),pow=this;for(i=0;i<l.length;i++)for(j=0;j<this.radix;j++){if(l[i]&1<<j)out=out.mul(pow);if(i==l.length-1&&l[i]>>j+1==0)break;pow=pow.square()}return out},mulmod:function(that,N){return this.mod(N).mul(that.mod(N)).mod(N)},powermod:function(x,N){if(x=new sjcl.bn(x),N=new sjcl.bn(N),(N.limbs[0]&1)==1){var montOut=this.montpowermod(x,N);if(montOut!=!1)return montOut}var i,j,l=x.normalize().trim().limbs,out=new this._class(1),pow=this;for(i=0;i<l.length;i++)for(j=0;j<this.radix;j++){if(l[i]&1<<j)out=out.mulmod(pow,N);if(i==l.length-1&&l[i]>>j+1==0)break;pow=pow.mulmod(pow,N)}return out},montpowermod:function(x,N){x=new sjcl.bn(x).normalize().trim(),N=new sjcl.bn(N);var i,j,radix=this.radix,out=new this._class(1),pow=this.copy(),R,s,wind,bitsize=x.bitLength();R=new sjcl.bn({limbs:N.copy().normalize().trim().limbs.map(function(){return 0})});for(s=this.radix;s>0;s--)if((N.limbs[N.limbs.length-1]>>s&1)==1){R.limbs[R.limbs.length-1]=1<<s;break}if(bitsize==0)return this;else if(bitsize<18)wind=1;else if(bitsize<48)wind=3;else if(bitsize<144)wind=4;else if(bitsize<768)wind=5;else wind=6;var RR=R.copy(),NN=N.copy(),RP=new sjcl.bn(1),NP=new sjcl.bn(0),RT=R.copy();while(RT.greaterEquals(1))if(RT.halveM(),(RP.limbs[0]&1)==0)RP.halveM(),NP.halveM();else RP.addM(NN),RP.halveM(),NP.halveM(),NP.addM(RR);RP=RP.normalize(),NP=NP.normalize(),RR.doubleM();var R2=RR.mulmod(RR,N);if(!RR.mul(RP).sub(N.mul(NP)).equals(1))return!1;var montIn=function(c){return montMul(c,R2)},montMul=function(a,b){var k,ab,right,abBar,mask=(1<<s+1)-1;if(ab=a.mul(b),right=ab.mul(NP),right.limbs=right.limbs.slice(0,R.limbs.length),right.limbs.length==R.limbs.length)right.limbs[R.limbs.length-1]&=mask;right=right.mul(N),abBar=ab.add(right).normalize().trim(),abBar.limbs=abBar.limbs.slice(R.limbs.length-1);for(k=0;k<abBar.limbs.length;k++){if(k>0)abBar.limbs[k-1]|=(abBar.limbs[k]&mask)<<radix-s-1;abBar.limbs[k]=abBar.limbs[k]>>s+1}if(abBar.greaterEquals(N))abBar.subM(N);return abBar},montOut=function(c){return montMul(c,1)};pow=montIn(pow),out=montIn(out);var h,precomp={},cap=(1<<wind-1)-1;precomp[1]=pow.copy(),precomp[2]=montMul(pow,pow);for(h=1;h<=cap;h++)precomp[2*h+1]=montMul(precomp[2*h-1],precomp[2]);var getBit=function(exp,i){var off=i%exp.radix;return(exp.limbs[Math.floor(i/exp.radix)]&1<<off)>>off};for(i=x.bitLength()-1;i>=0;)if(getBit(x,i)==0)out=montMul(out,out),i=i-1;else{var l=i-wind+1;while(getBit(x,l)==0)l++;var indx=0;for(j=l;j<=i;j++)indx+=getBit(x,j)<<j-l,out=montMul(out,out);out=montMul(out,precomp[indx]),i=l-1}return montOut(out)},trim:function(){var l=this.limbs,p;do p=l.pop();while(l.length&&p===0);return l.push(p),this},reduce:function(){return this},fullReduce:function(){return this.normalize()},normalize:function(){var carry=0,i,pv=this.placeVal,ipv=this.ipv,l,m,limbs=this.limbs,ll=limbs.length,mask=this.radixMask;for(i=0;i<ll||carry!==0&&carry!==-1;i++)l=(limbs[i]||0)+carry,m=limbs[i]=l&mask,carry=(l-m)*ipv;if(carry===-1)limbs[i-1]-=pv;return this.trim(),this},cnormalize:function(){var carry=0,i,ipv=this.ipv,l,m,limbs=this.limbs,ll=limbs.length,mask=this.radixMask;for(i=0;i<ll-1;i++)l=limbs[i]+carry,m=limbs[i]=l&mask,carry=(l-m)*ipv;return limbs[i]+=carry,this},toBits:function(len){this.fullReduce(),len=len||this.exponent||this.bitLength();var i=Math.floor((len-1)/24),w=sjcl.bitArray,e=(len+7&-8)%this.radix||this.radix,out=[w.partial(e,this.getLimb(i))];for(i--;i>=0;i--)out=w.concat(out,[w.partial(Math.min(this.radix,len),this.getLimb(i))]),len-=this.radix;return out},bitLength:function(){this.fullReduce();var out=this.radix*(this.limbs.length-1),b=this.limbs[this.limbs.length-1];for(;b;b>>>=1)out++;return out+7&-8}};sjcl.bn.fromBits=function(bits){var Class=this,out=new Class,words=[],w=sjcl.bitArray,t=this.prototype,l=Math.min(this.bitLength||4294967296,w.bitLength(bits)),e=l%t.radix||t.radix;words[0]=w.extract(bits,0,e);for(;e<l;e+=t.radix)words.unshift(w.extract(bits,e,t.radix));return out.limbs=words,out};sjcl.bn.prototype.ipv=1/(sjcl.bn.prototype.placeVal=Math.pow(2,sjcl.bn.prototype.radix));sjcl.bn.prototype.radixMask=(1<<sjcl.bn.prototype.radix)-1;sjcl.bn.pseudoMersennePrime=function(exponent,coeff){function p(it){this.initWith(it)}var ppr=p.prototype=new sjcl.bn,i,tmp,mo;mo=ppr.modOffset=Math.ceil(tmp=exponent/ppr.radix),ppr.exponent=exponent,ppr.offset=[],ppr.factor=[],ppr.minOffset=mo,ppr.fullMask=0,ppr.fullOffset=[],ppr.fullFactor=[],ppr.modulus=p.modulus=new sjcl.bn(Math.pow(2,exponent)),ppr.fullMask=0|-Math.pow(2,exponent%ppr.radix);for(i=0;i<coeff.length;i++)ppr.offset[i]=Math.floor(coeff[i][0]/ppr.radix-tmp),ppr.fullOffset[i]=Math.floor(coeff[i][0]/ppr.radix)-mo+1,ppr.factor[i]=coeff[i][1]*Math.pow(0.5,exponent-coeff[i][0]+ppr.offset[i]*ppr.radix),ppr.fullFactor[i]=coeff[i][1]*Math.pow(0.5,exponent-coeff[i][0]+ppr.fullOffset[i]*ppr.radix),ppr.modulus.addM(new sjcl.bn(Math.pow(2,coeff[i][0])*coeff[i][1])),ppr.minOffset=Math.min(ppr.minOffset,-ppr.offset[i]);return ppr._class=p,ppr.modulus.cnormalize(),ppr.reduce=function(){var i,k,l,mo=this.modOffset,limbs=this.limbs,off=this.offset,ol=this.offset.length,fac=this.factor,ll;i=this.minOffset;while(limbs.length>mo){l=limbs.pop(),ll=limbs.length;for(k=0;k<ol;k++)limbs[ll+off[k]]-=fac[k]*l;if(i--,!i)limbs.push(0),this.cnormalize(),i=this.minOffset}return this.cnormalize(),this},ppr._strongReduce=ppr.fullMask===-1?ppr.reduce:function(){var limbs=this.limbs,i=limbs.length-1,k,l;if(this.reduce(),i===this.modOffset-1){l=limbs[i]&this.fullMask,limbs[i]-=l;for(k=0;k<this.fullOffset.length;k++)limbs[i+this.fullOffset[k]]-=this.fullFactor[k]*l;this.normalize()}},ppr.fullReduce=function(){var greater,i;this._strongReduce(),this.addM(this.modulus),this.addM(this.modulus),this.normalize(),this._strongReduce();for(i=this.limbs.length;i<this.modOffset;i++)this.limbs[i]=0;greater=this.greaterEquals(this.modulus);for(i=0;i<this.limbs.length;i++)this.limbs[i]-=this.modulus.limbs[i]*greater;return this.cnormalize(),this},ppr.inverse=function(){return this.power(this.modulus.sub(2))},p.fromBits=sjcl.bn.fromBits,p};var sbp=sjcl.bn.pseudoMersennePrime;sjcl.bn.prime={p127:sbp(127,[[0,-1]]),p25519:sbp(255,[[0,-19]]),p192k:sbp(192,[[32,-1],[12,-1],[8,-1],[7,-1],[6,-1],[3,-1],[0,-1]]),p224k:sbp(224,[[32,-1],[12,-1],[11,-1],[9,-1],[7,-1],[4,-1],[1,-1],[0,-1]]),p256k:sbp(256,[[32,-1],[9,-1],[8,-1],[7,-1],[6,-1],[4,-1],[0,-1]]),p192:sbp(192,[[0,-1],[64,-1]]),p224:sbp(224,[[0,1],[96,-1]]),p256:sbp(256,[[0,-1],[96,1],[192,1],[224,-1]]),p384:sbp(384,[[0,-1],[32,1],[96,-1],[128,-1]]),p521:sbp(521,[[0,-1]])};sjcl.bn.random=function(modulus,paranoia){if(typeof modulus!=="object")modulus=new sjcl.bn(modulus);var words,i,l=modulus.limbs.length,m=modulus.limbs[l-1]+1,out=new sjcl.bn;while(!0){do if(words=sjcl.random.randomWords(l,paranoia),words[l-1]<0)words[l-1]+=4294967296;while(Math.floor(words[l-1]/m)===Math.floor(4294967296/m));words[l-1]%=m;for(i=0;i<l-1;i++)words[i]&=modulus.radixMask;if(out.limbs=words,!out.greaterEquals(modulus))return out}};if(typeof ArrayBuffer>"u")(function(globals){globals.ArrayBuffer=function(){},globals.DataView=function(){}})(null);sjcl.codec.arrayBuffer={fromBits:function(arr,padding,padding_count){var out,i,ol,tmp,smallest;if(padding=padding==null?!0:padding,padding_count=padding_count||8,arr.length===0)return new ArrayBuffer(0);if(ol=sjcl.bitArray.bitLength(arr)/8,sjcl.bitArray.bitLength(arr)%8!==0)throw new sjcl.exception.invalid("Invalid bit size, must be divisble by 8 to fit in an arraybuffer correctly");if(padding&&ol%padding_count!==0)ol+=padding_count-ol%padding_count;tmp=new DataView(new ArrayBuffer(arr.length*4));for(i=0;i<arr.length;i++)tmp.setUint32(i*4,arr[i]<<32);if(out=new DataView(new ArrayBuffer(ol)),out.byteLength===tmp.byteLength)return tmp.buffer;smallest=tmp.byteLength<out.byteLength?tmp.byteLength:out.byteLength;for(i=0;i<smallest;i++)out.setUint8(i,tmp.getUint8(i));return out.buffer},toBits:function(buffer){var i,out=[],len,inView,tmp;if(buffer.byteLength===0)return[];inView=new DataView(buffer),len=inView.byteLength-inView.byteLength%4;for(var i=0;i<len;i+=4)out.push(inView.getUint32(i));if(inView.byteLength%4!=0){tmp=new DataView(new ArrayBuffer(4));for(var i=0,l=inView.byteLength%4;i<l;i++)tmp.setUint8(i+4-l,inView.getUint8(len+i));out.push(sjcl.bitArray.partial(inView.byteLength%4*8,tmp.getUint32(0)))}return out},hexDumpBuffer:function(buffer){var stringBufferView=new DataView(buffer),string="",pad=function(n,width){return n=n+"",n.length>=width?n:Array(width-n.length+1).join("0")+n};for(var i=0;i<stringBufferView.byteLength;i+=2){if(i%16==0)string+=`
|
|
116
|
-
`+i.toString(16)+"\t";string+=pad(stringBufferView.getUint16(i).toString(16),4)+" "}if(typeof console===void 0)console=console||{log:function(){}};console.log(string.toUpperCase())}};var sjcl_default=sjcl;function assertNever(name,x){throw Error(`unexpected ${name} identifier: ${x}`)}function getHashParams(hash){switch(hash){case"SHA-1":return{name:hash,hLen:20};case"SHA-256":return{name:hash,hLen:32};case"SHA-384":return{name:hash,hLen:48};case"SHA-512":return{name:hash,hLen:64};default:assertNever("Hash",hash)}}function os2ip(bytes){return sjcl_default.bn.fromBits(sjcl_default.codec.bytes.toBits(Array.from(bytes)))}function i2osp(num,byteLength){if(Math.ceil(num.bitLength()/8)>byteLength)throw Error(`number does not fit in ${byteLength} bytes`);let bytes=new Uint8Array(byteLength),unpadded=new Uint8Array(sjcl_default.codec.bytes.fromBits(num.toBits(void 0)));return bytes.set(unpadded,byteLength-unpadded.length),bytes}function joinAll(a){let size=0;for(let ai of a)size+=ai.length;let ret=new Uint8Array(new ArrayBuffer(size)),offset=0;for(let ai of a)ret.set(ai,offset),offset+=ai.length;return ret}function xor(a,b){if(a.length!==b.length||a.length===0)throw Error(`arrays of different length: ${a.length} - ${b.length}`);let ai=a[Symbol.iterator](),bi=b[Symbol.iterator]();return new Uint8Array(a.length).map(()=>ai.next().value^bi.next().value)}function incCounter(c){if(c[3]++,c[3]!=0)return;if(c[2]++,c[2]!=0)return;if(c[1]++,c[1]!=0)return;c[0]++;return}async function mgf1(h,seed,mLen){let n=Math.ceil(mLen/h.hLen);if(n>Math.pow(2,32))throw Error("mask too long");let T=new Uint8Array,counter=new Uint8Array(4);for(let i=0;i<n;i++){let hash=new Uint8Array(await crypto.subtle.digest(h.name,joinAll([seed,counter]).slice().buffer));T=joinAll([T,hash]),incCounter(counter)}return T.subarray(0,mLen)}async function emsa_pss_encode(msg,emBits,opts,mgf=mgf1){let{hash,sLen}=opts,hashParams=getHashParams(hash),{hLen}=hashParams,emLen=Math.ceil(emBits/8),mHash=new Uint8Array(await crypto.subtle.digest(hash,msg.slice().buffer));if(emLen<hLen+sLen+2)throw Error("encoding error");let salt=crypto.getRandomValues(new Uint8Array(sLen)),mPrime=joinAll([new Uint8Array(8),mHash,salt]),h=new Uint8Array(await crypto.subtle.digest(hash,mPrime.slice().buffer)),ps=new Uint8Array(emLen-sLen-hLen-2),db=joinAll([ps,Uint8Array.of(1),salt]),dbMask=await mgf(hashParams,h,emLen-hLen-1),maskedDB=xor(db,dbMask);return maskedDB[0]&=255>>8*emLen-emBits,joinAll([maskedDB,h,Uint8Array.of(188)])}function rsavp1(pkS,s){if(!s.greaterEquals(new sjcl_default.bn(0))||s.greaterEquals(pkS.n))throw Error("signature representative out of range");return s.powermod(pkS.e,pkS.n)}function rsasp1(skS,m){if(!m.greaterEquals(new sjcl_default.bn(0))||m.greaterEquals(skS.n))throw Error("signature representative out of range");return m.powermod(skS.d,skS.n)}function is_coprime(x,n){try{x.inverseMod(n)}catch{return!1}return!0}function random_integer_uniform(n,kLen){for(let i=0;i<128;i++){let r=os2ip(crypto.getRandomValues(new Uint8Array(kLen)));if(!(r.greaterEquals(n)||r.equals(0)))return r}throw Error("reached maximum tries for random integer generation")}var NATIVE_SUPPORT_NAME="RSA-RAW";async function rsaRawBlingSign(privateKey,blindMsg){if(privateKey.algorithm.name!==NATIVE_SUPPORT_NAME)privateKey=await crypto.subtle.importKey("pkcs8",await crypto.subtle.exportKey("pkcs8",privateKey),{...privateKey.algorithm,name:NATIVE_SUPPORT_NAME},privateKey.extractable,privateKey.usages);let signature=await crypto.subtle.sign({name:privateKey.algorithm.name},privateKey,blindMsg.slice().buffer);return new Uint8Array(signature)}var PrepareType;(function(PrepareType){PrepareType[PrepareType.Deterministic=0]="Deterministic",PrepareType[PrepareType.Randomized=32]="Randomized"})(PrepareType||(PrepareType={}));class BlindRSA{params;static NAME="RSA-PSS";constructor(params){switch(this.params=params,params.prepareType){case PrepareType.Deterministic:case PrepareType.Randomized:return;default:assertNever("PrepareType",params.prepareType)}}toString(){let hash=this.params.hash.replace("-",""),pssType="PSS"+(this.params.saltLength===0?"ZERO":""),prepare=PrepareType[this.params.prepareType];return`RSABSSA-${hash}-${pssType}-${prepare}`}prepare(msg){let msg_prefix_len=this.params.prepareType,msg_prefix=crypto.getRandomValues(new Uint8Array(msg_prefix_len));return joinAll([msg_prefix,msg])}async extractKeyParams(key,type){if(key.type!==type||key.algorithm.name!==BlindRSA.NAME)throw Error(`key is not ${BlindRSA.NAME}`);if(!key.extractable)throw Error("key is not extractable");let{modulusLength:modulusLengthBits,hash:hashFn}=key.algorithm,modulusLengthBytes=Math.ceil(modulusLengthBits/8),hash=hashFn.name;if(hash.toLowerCase()!==this.params.hash.toLowerCase())throw Error(`hash is not ${this.params.hash}`);return{jwkKey:await crypto.subtle.exportKey("jwk",key),modulusLengthBits,modulusLengthBytes,hash}}async blind(publicKey,msg){let{jwkKey,modulusLengthBits:modulusLength,modulusLengthBytes:kLen,hash}=await this.extractKeyParams(publicKey,"public");if(!jwkKey.n||!jwkKey.e)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n)),pk={e:sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.e)),n},opts={sLen:this.params.saltLength,hash},encoded_msg=await emsa_pss_encode(msg,modulusLength-1,opts),m=os2ip(encoded_msg);if(!is_coprime(m,n))throw Error("invalid input");let r=random_integer_uniform(n,kLen),inv;try{inv=i2osp(r.inverseMod(n),kLen)}catch(e){throw Error(`blinding error: ${e.toString()}`)}let x=rsavp1(pk,r),z=m.mulmod(x,n);return{blindedMsg:i2osp(z,kLen),inv}}async blindSign(privateKey,blindMsg){if(this.params.supportsRSARAW)return rsaRawBlingSign(privateKey,blindMsg);let{jwkKey,modulusLengthBytes:kLen}=await this.extractKeyParams(privateKey,"private");if(!jwkKey.n||!jwkKey.d||!jwkKey.e)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n)),d=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.d)),e=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.e)),sk={n,d},pk={n,e},m=os2ip(blindMsg),s=rsasp1(sk,m),mp=rsavp1(pk,s);if(!m.equals(mp))throw Error("signing failure");return i2osp(s,kLen)}async finalize(publicKey,msg,blindSig,inv){let{jwkKey,modulusLengthBytes:kLen}=await this.extractKeyParams(publicKey,"public");if(!jwkKey.n)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n));if(inv.length!=kLen)throw Error("unexpected input size");let rInv=os2ip(inv);if(blindSig.length!=kLen)throw Error("unexpected input size");let s=os2ip(blindSig).mulmod(rInv,n),sig=i2osp(s,kLen),algorithm={name:BlindRSA.NAME,saltLength:this.params.saltLength};if(!await crypto.subtle.verify(algorithm,publicKey,sig.slice().buffer,msg.slice().buffer))throw Error("invalid signature");return sig}static generateKey(algorithm){return crypto.subtle.generateKey({...algorithm,name:BlindRSA.NAME},!0,["sign","verify"])}generateKey(algorithm){return BlindRSA.generateKey({...algorithm,hash:this.params.hash})}verify(publicKey,signature,message){return crypto.subtle.verify({name:BlindRSA.NAME,saltLength:this.params.saltLength},publicKey,signature.slice().buffer,message.slice().buffer)}}var Params={RSABSSA_SHA384_PSS_Randomized:{name:"RSABSSA-SHA384-PSS-Randomized",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Randomized},RSABSSA_SHA384_PSS_Deterministic:{name:"RSABSSA-SHA384-PSS-Deterministic",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Deterministic},RSABSSA_SHA384_PSSZERO_Randomized:{name:"RSABSSA-SHA384-PSSZERO-Randomized",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Randomized},RSABSSA_SHA384_PSSZERO_Deterministic:{name:"RSABSSA-SHA384-PSSZERO-Deterministic",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Deterministic},RSAPBSSA_SHA384_PSS_Randomized:{name:"RSAPBSSA-SHA384-PSS-Randomized",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Randomized},RSAPBSSA_SHA384_PSS_Deterministic:{name:"RSAPBSSA-SHA384-PSS-Deterministic",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Deterministic},RSAPBSSA_SHA384_PSSZERO_Randomized:{name:"RSAPBSSA-SHA384-PSSZERO-Randomized",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Randomized},RSAPBSSA_SHA384_PSSZERO_Deterministic:{name:"RSAPBSSA-SHA384-PSSZERO-Deterministic",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Deterministic}},RSABSSA={SHA384:{generateKey:(algorithm)=>BlindRSA.generateKey({...algorithm,hash:"SHA-384"}),PSS:{Randomized:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSS_Randomized,...params}),Deterministic:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSS_Deterministic,...params})},PSSZero:{Randomized:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSSZERO_Randomized,...params}),Deterministic:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSSZERO_Deterministic,...params})}}};var PINNED_ISSUER_KEYS={"https://api.velloo.ai":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs54VddkMfc1b0S+2i8GOouyseMo4mb6+zPYBv/Omu1sZQfTYFVFttZOWO0vw6ObT8EVBC/4qCgZPa1d8To55rNe6ZSWJkvXFuoEeOaVDlfDhxduFnVFa5QelxEwVXAFpVSSdPwcFicoCNxuhHTbezPQl3yKLZVwIUncMNhpwsjXVyrKEtSp+5cAY1SH2axLBpqPT1nReH5HAZmAmUcZFvJND45htig2o2tvFSTkOCurFt6lw9HiCWjNE1L1C3Y/VuGkvIVbQShNipyBvgn1ZtJxFvkY4M4ULUj9G9JMb6pYtUupUddPLfpROXSPFvbDk22JOOP2VWtv5r9mXeTCAIQIDAQAB"};var TOKEN_SCHEME="RSABSSA-SHA384.PSS.Randomized",suite=RSABSSA.SHA384.PSS.Randomized(),KEY_ALG={name:"RSA-PSS",hash:"SHA-384"},BATCH=10,defaultTokenStorePath=()=>join14(homedir3(),".velloo","feedback-tokens.json");async function loadStore(path){try{return JSON.parse(await readFile6(path,"utf8"))}catch{return{}}}async function saveStore(path,store){await mkdir8(dirname10(path),{recursive:!0}),await writeJsonAtomic(path,store)}var originOf=(url)=>new URL(url).origin;async function fetchIssuerKey(cloudUrl,prior){let res=await fetch(`${cloudUrl}/v1/feedback/token-key`,{signal:AbortSignal.timeout(5000)});if(!res.ok)throw new TokenIssueError("rejected",`token key fetch failed (${res.status})`);let parsed=FeedbackIssuerResponseSchema.safeParse(await res.json().catch(()=>null));if(!parsed.success)throw Error("the cloud's feedback token key reply was malformed");let{scheme,publicKey}=parsed.data;if(scheme!==TOKEN_SCHEME)throw Error(`unsupported feedback token scheme ${JSON.stringify(scheme)}`);let pinned=PINNED_ISSUER_KEYS[originOf(cloudUrl)];if(pinned&&publicKey!==pinned)throw Error("the cloud's feedback issuer key does not match the key pinned in this velloo build \u2014 refusing to fetch tokens (a per-user key would deanonymize feedback).");if(!pinned&&prior&&prior.publicKey!==publicKey)throw Error(`the cloud's feedback issuer key changed since tokens were first issued \u2014 refusing to fetch more. If the rotation is expected, delete ${defaultTokenStorePath()} and retry.`);return{publicKey:await crypto.subtle.importKey("spki",Buffer.from(publicKey,"base64"),KEY_ALG,!0,["verify"]),spkiB64:publicKey}}async function topUpTokens(cloud,min=1,storePath=defaultTokenStorePath()){let store=await loadStore(storePath),origin=originOf(cloud.url),entry=store[origin];if((entry?.tokens.length??0)>=min)return;if(!cloud.token)throw new TokenIssueError("signed-out","not signed in \u2014 run `velloo login` to fetch feedback tokens");let issuer=await fetchIssuerKey(cloud.url,entry),blinds=[];for(let i=0;i<BATCH;i++){let prepared=suite.prepare(crypto.getRandomValues(new Uint8Array(32)));blinds.push({prepared,...await suite.blind(issuer.publicKey,prepared)})}let res=await fetch(`${cloud.url}/v1/feedback/tokens`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${cloud.token}`},body:JSON.stringify({blinded:blinds.map((b)=>Buffer.from(b.blindedMsg).toString("base64"))}),signal:AbortSignal.timeout(15000)});if(!res.ok)throw new TokenIssueError(res.status===402||res.status===429?"no-tokens":"rejected",`token issuance failed (${res.status})`);let issued=FeedbackSignaturesResponseSchema.safeParse(await res.json().catch(()=>null));if(!issued.success||issued.data.signatures.length!==blinds.length)throw Error("token issuance returned a malformed signature batch");let{signatures}=issued.data,tokens=[];for(let i=0;i<blinds.length;i++){let b=blinds[i];if(!b)continue;let sig=await suite.finalize(issuer.publicKey,b.prepared,Buffer.from(signatures[i]??"","base64"),b.inv);if(!await suite.verify(issuer.publicKey,sig,b.prepared))throw Error("issued token failed local verification \u2014 not storing it");tokens.push({token:Buffer.from(b.prepared).toString("base64"),sig:Buffer.from(sig).toString("base64")})}store[origin]={scheme:TOKEN_SCHEME,publicKey:issuer.spkiB64,tokens:[...entry?.tokens??[],...tokens]},await saveStore(storePath,store)}async function popToken(cloudUrl,storePath){let store=await loadStore(storePath),entry=store[originOf(cloudUrl)],token=entry?.tokens.shift();if(!entry||!token)return null;return await saveStore(storePath,store),token}function feedbackError(reason,message,retryable){return{kind:"FeedbackNotSent",reason,message,retryable}}class TokenIssueError extends Error{reason;constructor(reason,message){super(message);this.reason=reason}}async function sendAnonymousFeedback(cloud,payload,storePath=defaultTokenStorePath()){for(let attempt=0;attempt<2;attempt++){try{await topUpTokens(cloud,1,storePath)}catch(e){let message=e instanceof Error?e.message:String(e);if(e instanceof TokenIssueError)return err(feedbackError(e.reason,message,e.reason!=="signed-out"));return err(feedbackError("unreachable",message,!0))}let token=await popToken(cloud.url,storePath);if(!token)return err(feedbackError("no-tokens","no anonymous feedback tokens available for this account",!0));try{let res=await fetch(`${cloud.url}/v1/feedback/anonymous`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({body:payload.body,...payload.toolVersion?{toolVersion:payload.toolVersion}:{},...payload.source?{source:payload.source}:{},token:token.token,signature:token.sig}),signal:AbortSignal.timeout(5000)});if(res.status===409)continue;if(!res.ok)return err(feedbackError("rejected",`velloo-cloud refused the message (${res.status}).`,!1));return ok("Thanks \u2014 your feedback was sent (anonymously).")}catch{return err(feedbackError("unreachable","Couldn't reach velloo-cloud; feedback not sent.",!0))}}return err(feedbackError("rejected","feedback tokens kept colliding \u2014 try again later",!0))}import{existsSync as existsSync4}from"fs";import{isAbsolute,resolve as resolve5}from"path";function createServerProviderLoader(folderRoot,hostApp){let hostAppRoot=folderRoot?hostAppRootFrom(folderRoot,hostApp):void 0;return createProviderLoader({none:async()=>(await import("./chunk-xr6f94rj.js")).createProvider(),mui:async()=>(await import("./chunk-rksq3w72.js")).createProvider(),antd:async()=>(await import("./chunk-2vx420g2.js")).createProvider(),chakra:async()=>(await import("./chunk-b2ebm1t3.js")).createProvider(),"shadcn-upstream":async(library)=>{let cacheDir=resolveUpstreamCacheDir(library,folderRoot);return(await import("./chunk-5js0qw32.js")).createProvider({version:library.version,...cacheDir?{cacheDir}:{},...hostAppRoot?{hostAppRoot}:{}})}})}function resolveUpstreamCacheDir(library,folderRoot){let path=library.componentsPath;if(!path||path==="binary")return null;if(path.startsWith("~/")){let home=process.env.HOME??process.env.USERPROFILE??"";if(!home)return null;let abs=resolve5(home,path.slice(2));return existsSync4(abs)?abs:null}if(isAbsolute(path))return library.source==="in-repo"||existsSync4(path)?path:null;if(!folderRoot)return null;let abs=resolveProjectPath(folderRoot,path);return library.source==="in-repo"||existsSync4(abs)?abs:null}async function resolveProviders(config,folderRoot,loader){let effective=loader??createServerProviderLoader(folderRoot,config.hostApp),providers={};for(let[libraryId,library]of Object.entries(config.libraries))try{providers[libraryId]=await effective(library)}catch(err){if(err instanceof UnknownProviderError)throw Error(`velloo: this binary doesn't know how to load library "${err.id}" (registered as "${libraryId}"). Upgrade velloo or pick a supported provider in .design/config.json.`);throw err}let defaultProvider=providers[config.defaultLibrary];if(!defaultProvider)throw Error(`velloo: defaultLibrary "${config.defaultLibrary}" doesn't match any registered library.`);if(config.styling){let css=config.styling.framework,wanted=CSS_FRAMEWORK_CHANNEL[css];if(!Object.values(providers).some((p)=>styleChannelOf(p,css).kind===wanted)){let libs=Object.values(config.libraries).map((l)=>l.id).join(", ");throw Error(`velloo: config.styling.framework "${css}" isn't supported by any registered library (${libs}). shadcn and MUI carry their own styling; only the no-framework library can pair with "none". Fix the CSS framework or library in .design/config.json.`)}}return{providers,defaultProvider}}import{existsSync as existsSync5}from"fs";import{resolve as resolve6}from"path";var CONFIG_NAMES=["tailwind.config.ts","tailwind.config.js","tailwind.config.cjs","tailwind.config.mjs"];function findHostTailwindConfig(folderRoot,hostApp){let root=hostAppRootFrom(folderRoot,hostApp);for(let name of CONFIG_NAMES){let path=resolve6(root,name);if(existsSync5(path))return path}return null}import{readFile as readFile7}from"fs/promises";import{dirname as dirname11,join as join15}from"path";import{compile}from"@tailwindcss/node";import{Scanner}from"@tailwindcss/oxide";class TailwindJit{pagesDir;extraEntryCss;extraSourceDirs;hostConfigPath;compilerPromise=null;cached=null;cachedCandidates=null;snippetsDir;providers;tailwindProviders;constructor(providers,pagesDir,snippetsDir,extraEntryCss,extraSourceDirs,hostConfigPath,folderCss){this.pagesDir=pagesDir;this.extraEntryCss=extraEntryCss;this.extraSourceDirs=extraSourceDirs;this.hostConfigPath=hostConfigPath;if(this.providers=Array.isArray(providers)?providers:[providers],this.providers.length===0)throw Error("TailwindJit: at least one provider is required.");this.tailwindProviders=this.providers.filter((p)=>styleChannelOf(p,folderCss).needsTailwindJit),this.snippetsDir=snippetsDir??join15(pagesDir,"..","snippets")}invalidate(){this.cached=null,this.cachedCandidates=null,this.compilerPromise=null}async build(extraCandidates){if(this.tailwindProviders.length===0)return"";let hasExtra=extraCandidates!==void 0&&extraCandidates.length>0;if(!hasExtra&&this.cached!==null)return this.cached;let compiler=await this.getCompiler(),candidates=this.scanCandidates();if(!hasExtra)return this.cached=compiler.build(candidates),this.cached;return compiler.build([...candidates,...extraCandidates])}scanCandidates(){if(this.cachedCandidates!==null)return this.cachedCandidates;let dedupedDirs=Array.from(new Set([...this.tailwindProviders.map((p)=>p.componentsDir),helpersComponentsDir])),hostDirs=Array.from(new Set(this.extraSourceDirs?.()??[])),scanner=new Scanner({sources:[...dedupedDirs.map((base)=>({base,pattern:"**/*.tsx",negated:!1})),...hostDirs.map((base)=>({base,pattern:"**/*.tsx",negated:!1})),{base:this.pagesDir,pattern:"**/*.json",negated:!1},{base:this.snippetsDir,pattern:"**/*.json",negated:!1}]});return this.cachedCandidates=scanner.scan(),this.cachedCandidates}entryCss(){return this.mergedEntryCss()}async mergedEntryCss(){let primary=this.tailwindProviders[0];if(!primary)throw Error("TailwindJit: no Tailwind-channel provider, cannot build entry CSS.");let primaryBase=dirname11(primary.styleEntryPath),extra=this.extraEntryCss?.()??"";if(this.tailwindProviders.length===1){let css=await readFile7(primary.styleEntryPath,"utf8");return{css:extra?`${css}
|
|
115
|
+
`}function registryFor(source){let{folder}=source;return(screen)=>registryForScreen(screen,source.providers,source.defaultProvider,folder.config.extensions??{},folder.config.styling?.framework)}function boardsPlacing(folder,screenId){return[...folder.boards.values()].filter((board)=>board.frames.some((frame)=>frame.screen===screenId)).map((board)=>({id:board.id,name:board.name}))}function preflightScreens(source,screens){let registry=registryFor(source),out=[];for(let screen of screens){let where={screenId:screen.id,screenName:screen.name,boards:boardsPlacing(source.folder,screen.id)};try{for(let failure of renderBodyGuarded(screen,registry(screen),source.folder.snippets).failures)out.push({...where,componentId:failure.componentId,reason:failure.reason})}catch(error){if(error instanceof RenderGuardLimitError)for(let failure of error.failures)out.push({...where,componentId:failure.componentId,reason:failure.reason});out.push({...where,componentId:null,reason:error instanceof Error?error.message:String(error)})}}return out}function screenById(folder,id){return id===void 0?void 0:folder.screens.get(id)}function screensForBoards(folder,boardIds){let seen=new Map;for(let boardId of boardIds){let board=folder.boards.get(boardId);if(!board)continue;for(let frame of board.frames){let screen=screenById(folder,frame.screen);if(screen&&!seen.has(screen.id))seen.set(screen.id,screen)}}return[...seen.values()]}function screensForExportTarget(folder,kind,id){if(kind==="board")return screensForBoards(folder,[id]);if(kind==="screen"){let screen=folder.screens.get(id);return screen?[screen]:[]}for(let board of folder.boards.values()){let frame=board.frames.find((f)=>f.id===id);if(!frame)continue;let screen=screenById(folder,frame.screen);return screen?[screen]:[]}return[]}import{existsSync as existsSync4}from"fs";import{dirname as dirname9,join as join13}from"path";import{fileURLToPath}from"url";var here=dirname9(fileURLToPath(import.meta.url));function resolveDir(){return[process.env.VELLOO_HELPERS_SRC,join13(here,"pkgs","helpers","src"),here].filter((p)=>Boolean(p)).find((d)=>existsSync4(join13(d,"heading.tsx")))??here}var helpersComponentsDir=resolveDir(),HELPER_SOURCE_FILES={Box:"box.tsx",Divider:"divider.tsx",Gradient:"gradient.tsx",Heading:"heading.tsx",Icon:"icon.tsx",Image:"image.tsx",Layer:"layer.tsx",Placeholder:"placeholder.tsx",Prose:"prose.tsx",SVG:"svg.tsx",Text:"text.tsx"};function helperSourcePath(id){let file=HELPER_SOURCE_FILES[id];return file?join13(helpersComponentsDir,file):void 0}import{existsSync as existsSync5,readdirSync as readdirSync2,readFileSync as readFileSync4,realpathSync as realpathSync3}from"fs";import{join as join14,relative as relative2,resolve as resolve5}from"path";var SWITCH_DESIGN_META="velloo/switchDesign",PICK_REASONS=["cwd","only","default","arbitrary"];function parseMcpSessionUrl(url){let params=new URL(url??"/mcp","http://velloo.local").searchParams,pick=params.get("pick");return{switchable:params.get("switch")==="1",pick:PICK_REASONS.find((reason)=>reason===pick)}}function withMcpSessionUrl(url,session){let parsed=new URL(url);if(session.switchable)parsed.searchParams.set("switch","1");else parsed.searchParams.delete("switch");if(session.pick)parsed.searchParams.set("pick",session.pick);else parsed.searchParams.delete("pick");return parsed.toString()}function realOr3(path){return existsSync5(path)?realpathSync3(path):resolve5(path)}function usableDesigns(set){return(set?.designs??[]).filter((d)=>d.exists&&!d.outsideRepo&&set?.byName.get(d.name)===d)}async function sessionDesigns(root,current,options){let set=await designsFor(root).catch(()=>null),here=realOr3(root),others=usableDesigns(set).filter((d)=>realOr3(d.root)!==here);if(others.length===0)return null;let names=[...new Set([current,...others.map((d)=>d.name)])].sort();return{current,names,switchable:options.switchable,pick:options.pick}}function designsInstruction(designs){let{current,names}=designs,list=names.map((n)=>n===current?`\`${n}\` (this session)`:`\`${n}\``),move=designs.switchable?"`switch_design` moves to another":"to work on another, the user reconnects with `velloo mcp <name>`",lines=[`**Design: \`${current}\`** \u2014 one of ${names.length} here: ${list.join(", ")}. Operations target \`${current}\`; ${move}. Tell the user when you switch.`];if(designs.pick==="arbitrary")lines.unshift(`**No design was specified, so this session opened \`${current}\`.** Confirm which design the user means before changing anything.`);return lines}function countJson(dir,skipSuffix){try{return readdirSync2(dir).filter((f)=>f.endsWith(".json")&&!f.endsWith(skipSuffix)).length}catch{return 0}}function defaultLibraryOf(folder){try{let config=JSON.parse(readFileSync4(join14(folder,".design","config.json"),"utf8")),key=typeof config.defaultLibrary==="string"?config.defaultLibrary:void 0,id=key?config.libraries?.[key]?.id:void 0;return typeof id==="string"?id:void 0}catch{return}}async function listDesigns(root){let set=await designsFor(root),here=realOr3(root);return usableDesigns(set).map((d)=>({name:d.name,path:d.local||!set?.repo?d.root:relative2(set.repo.dir,d.root)||".",current:realOr3(d.root)===here,local:d.local!==null,library:defaultLibraryOf(d.root),boards:countJson(join14(d.root,"boards"),".notes.json"),screens:countJson(join14(d.root,"screens"),".annotations.json")}))}async function switchTarget(root,name){let set=await designsFor(root),target=usableDesigns(set).find((d)=>d.name===name);if(!target)return{ok:!1,error:{kind:"UnknownDesign",name,designs:usableDesigns(set).map((d)=>d.name)}};return{ok:!0,directive:{name:target.name,root:target.root}}}function jsonResult(value){return{content:[{type:"text",text:JSON.stringify(value)}]}}function structuredResult(value,...extra){return{content:[{type:"text",text:JSON.stringify(value)},...extra],structuredContent:value}}function errorResult(error){return{isError:!0,content:[{type:"text",text:JSON.stringify(typeof error==="string"?{kind:"Error",message:error}:error)}]}}function toMcp(result){return result.ok?jsonResult(result.value):errorResult(result.error)}var MCP_SURFACE_MODES=["guided","full"],DEFAULT_MCP_SURFACE={mode:"guided"},LEGACY_SURFACE_ALIASES={profile:"full"};function parseMcpSurfaceSelection(modeValue){let raw=modeValue||DEFAULT_MCP_SURFACE.mode,mode=LEGACY_SURFACE_ALIASES[raw]??raw;if(!MCP_SURFACE_MODES.includes(mode))return{ok:!1,error:`unknown MCP surface "${raw}"; expected ${MCP_SURFACE_MODES.join(", ")}`};return{ok:!0,selection:{mode}}}function parseMcpSurfaceUrl(url){let parsed=new URL(url??"/mcp","http://velloo.local");return parseMcpSurfaceSelection(parsed.searchParams.get("surface"))}function withMcpSurfaceUrl(url,selection){let parsed=new URL(url);return parsed.searchParams.set("surface",selection.mode),parsed.searchParams.delete("profile"),parsed.toString()}function schemaJson(tool){if(!tool.inputSchema)return{type:"object",properties:{},additionalProperties:!1};try{return toJSONSchema(tool.inputSchema,{io:"input"})}catch{return{type:"object",description:"Schema is not representable as JSON Schema"}}}function operationHelp(operation,tool){return{operation,description:tool.description??"",inputSchema:schemaJson(tool),annotations:tool.annotations??{}}}function appendSchemaHelp(result,operation,tool){if(result.isError!==!0)return result;return{...result,content:[...result.content,{type:"text",text:JSON.stringify({kind:"OperationSchemaHelp",...operationHelp(operation,tool)})}]}}function applyMcpToolSurface(mcp,selection){let original=mcp.registerTool.bind(mcp),native=new Map,registeringNative=!0,patched=(name,config,cb)=>{let registered=original(name,config,cb);if(registeringNative){if(native.set(name,registered),selection.mode!=="full")registered.disable()}return registered};mcp.registerTool=patched;let invoke=async(operation,args,extra)=>{let tool=native.get(operation);if(!tool)return errorResult({kind:"UnknownOperation",operation});if(tool.inputSchema){let parsed=await tool.inputSchema.safeParseAsync(args);if(!parsed.success)return errorResult({kind:"InvalidOperationArguments",operation,issues:parsed.error.issues,...operationHelp(operation,tool)});return appendSchemaHelp(await tool.handler(parsed.data,extra),operation,tool)}return appendSchemaHelp(await tool.handler(extra,void 0),operation,tool)};return{nativeTools:()=>native,finish(){if(registeringNative=!1,selection.mode!=="guided")return;let operation=_enum([...native.keys()]);mcp.registerTool("call_velloo",{description:"Call one native Velloo operation. Failed calls include the exact correction schema.",inputSchema:{operation,arguments:record(string(),unknown()).describe("Arguments object for the native operation")}},async({operation:name,arguments:args},extra)=>invoke(name,args,extra)),mcp.registerTool("run_velloo_plan",{description:"Run up to eight native Velloo calls sequentially, stopping on the first error by default.",inputSchema:{calls:array(object({operation,arguments:record(string(),unknown())})).min(1).max(8),stopOnError:boolean().default(!0)}},async({calls,stopOnError},extra)=>{let content=[],failedAt=null;for(let[index,call]of calls.entries()){let result=call.operation==="switch_design"?errorResult({kind:"SwitchDesignInPlan",message:"Call switch_design on its own, then plan against the new design."}):await invoke(call.operation,call.arguments,extra);if(content.push({type:"text",text:JSON.stringify({index,operation:call.operation,isError:result.isError===!0})}),content.push(...result.content),result.isError===!0&&stopOnError!==!1){failedAt=index;break}}let summary=failedAt===null?{kind:"PlanCompleted",completed:calls.length}:{kind:"PlanFailed",failedAt,completed:failedAt+1};return{...failedAt===null?{}:{isError:!0},content:[{type:"text",text:JSON.stringify(summary)},...content]}}),mcp.registerTool("operation_schema",{description:"Return the exact schema and description for one native operation.",inputSchema:{operation}},async({operation:name})=>{let tool=native.get(name);return tool?jsonResult(operationHelp(name,tool)):errorResult({kind:"UnknownOperation",operation:name})})}}}import{mkdir as mkdir8,readFile as readFile6}from"fs/promises";import{homedir as homedir3}from"os";import{dirname as dirname10,join as join15}from"path";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(message){this.toString=function(){return"CORRUPT: "+this.message},this.message=message},invalid:function(message){this.toString=function(){return"INVALID: "+this.message},this.message=message},bug:function(message){this.toString=function(){return"BUG: "+this.message},this.message=message},notReady:function(message){this.toString=function(){return"NOT READY: "+this.message},this.message=message}}};sjcl.cipher.aes=function(key){if(!this._tables[0][0][0])this._precompute();var i,j,tmp,encKey,decKey,sbox=this._tables[0][4],decTable=this._tables[1],keyLen=key.length,rcon=1;if(keyLen!==4&&keyLen!==6&&keyLen!==8)throw new sjcl.exception.invalid("invalid aes key size");this._key=[encKey=key.slice(0),decKey=[]];for(i=keyLen;i<4*keyLen+28;i++){if(tmp=encKey[i-1],i%keyLen===0||keyLen===8&&i%keyLen===4){if(tmp=sbox[tmp>>>24]<<24^sbox[tmp>>16&255]<<16^sbox[tmp>>8&255]<<8^sbox[tmp&255],i%keyLen===0)tmp=tmp<<8^tmp>>>24^rcon<<24,rcon=rcon<<1^(rcon>>7)*283}encKey[i]=encKey[i-keyLen]^tmp}for(j=0;i;j++,i--)if(tmp=encKey[j&3?i:i-4],i<=4||j<4)decKey[j]=tmp;else decKey[j]=decTable[0][sbox[tmp>>>24]]^decTable[1][sbox[tmp>>16&255]]^decTable[2][sbox[tmp>>8&255]]^decTable[3][sbox[tmp&255]]};sjcl.cipher.aes.prototype={encrypt:function(data){return this._crypt(data,0)},decrypt:function(data){return this._crypt(data,1)},_tables:[[[],[],[],[],[]],[[],[],[],[],[]]],_precompute:function(){var encTable=this._tables[0],decTable=this._tables[1],sbox=encTable[4],sboxInv=decTable[4],i,x,xInv,d=[],th=[],x2,x4,x8,s,tEnc,tDec;for(i=0;i<256;i++)th[(d[i]=i<<1^(i>>7)*283)^i]=i;for(x=xInv=0;!sbox[x];x^=x2||1,xInv=th[xInv]||1){s=xInv^xInv<<1^xInv<<2^xInv<<3^xInv<<4,s=s>>8^s&255^99,sbox[x]=s,sboxInv[s]=x,x8=d[x4=d[x2=d[x]]],tDec=x8*16843009^x4*65537^x2*257^x*16843008,tEnc=d[s]*257^s*16843008;for(i=0;i<4;i++)encTable[i][x]=tEnc=tEnc<<24^tEnc>>>8,decTable[i][s]=tDec=tDec<<24^tDec>>>8}for(i=0;i<5;i++)encTable[i]=encTable[i].slice(0),decTable[i]=decTable[i].slice(0)},_crypt:function(input,dir){if(input.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var key=this._key[dir],a=input[0]^key[0],b=input[dir?3:1]^key[1],c=input[2]^key[2],d=input[dir?1:3]^key[3],a2,b2,c2,nInnerRounds=key.length/4-2,i,kIndex=4,out=[0,0,0,0],table=this._tables[dir],t0=table[0],t1=table[1],t2=table[2],t3=table[3],sbox=table[4];for(i=0;i<nInnerRounds;i++)a2=t0[a>>>24]^t1[b>>16&255]^t2[c>>8&255]^t3[d&255]^key[kIndex],b2=t0[b>>>24]^t1[c>>16&255]^t2[d>>8&255]^t3[a&255]^key[kIndex+1],c2=t0[c>>>24]^t1[d>>16&255]^t2[a>>8&255]^t3[b&255]^key[kIndex+2],d=t0[d>>>24]^t1[a>>16&255]^t2[b>>8&255]^t3[c&255]^key[kIndex+3],kIndex+=4,a=a2,b=b2,c=c2;for(i=0;i<4;i++)out[dir?3&-i:i]=sbox[a>>>24]<<24^sbox[b>>16&255]<<16^sbox[c>>8&255]<<8^sbox[d&255]^key[kIndex++],a2=a,a=b,b=c,c=d,d=a2;return out}};sjcl.bitArray={bitSlice:function(a,bstart,bend){return a=sjcl.bitArray._shiftRight(a.slice(bstart/32),32-(bstart&31)).slice(1),bend===void 0?a:sjcl.bitArray.clamp(a,bend-bstart)},extract:function(a,bstart,blength){var x,sh=Math.floor(-bstart-blength&31);if((bstart+blength-1^bstart)&-32)x=a[bstart/32|0]<<32-sh^a[bstart/32+1|0]>>>sh;else x=a[bstart/32|0]>>>sh;return x&(1<<blength)-1},concat:function(a1,a2){if(a1.length===0||a2.length===0)return a1.concat(a2);var last=a1[a1.length-1],shift=sjcl.bitArray.getPartial(last);if(shift===32)return a1.concat(a2);else return sjcl.bitArray._shiftRight(a2,shift,last|0,a1.slice(0,a1.length-1))},bitLength:function(a){var l=a.length,x;if(l===0)return 0;return x=a[l-1],(l-1)*32+sjcl.bitArray.getPartial(x)},clamp:function(a,len){if(a.length*32<len)return a;a=a.slice(0,Math.ceil(len/32));var l=a.length;if(len=len&31,l>0&&len)a[l-1]=sjcl.bitArray.partial(len,a[l-1]&2147483648>>len-1,1);return a},partial:function(len,x,_end){if(len===32)return x;return(_end?x|0:x<<32-len)+len*1099511627776},getPartial:function(x){return Math.round(x/1099511627776)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var x=0,i;for(i=0;i<a.length;i++)x|=a[i]^b[i];return x===0},_shiftRight:function(a,shift,carry,out){var i,last2=0,shift2;if(out===void 0)out=[];for(;shift>=32;shift-=32)out.push(carry),carry=0;if(shift===0)return out.concat(a);for(i=0;i<a.length;i++)out.push(carry|a[i]>>>shift),carry=a[i]<<32-shift;return last2=a.length?a[a.length-1]:0,shift2=sjcl.bitArray.getPartial(last2),out.push(sjcl.bitArray.partial(shift+shift2&31,shift+shift2>32?carry:out.pop(),1)),out},_xor4:function(x,y){return[x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]},byteswapM:function(a){var i,v,m=65280;for(i=0;i<a.length;++i)v=a[i],a[i]=v>>>24|v>>>8&m|(v&m)<<8|v<<24;return a}};sjcl.codec.utf8String={fromBits:function(arr){var out="",bl=sjcl.bitArray.bitLength(arr),i,tmp;for(i=0;i<bl/8;i++){if((i&3)===0)tmp=arr[i/4];out+=String.fromCharCode(tmp>>>8>>>8>>>8),tmp<<=8}return decodeURIComponent(escape(out))},toBits:function(str){str=unescape(encodeURIComponent(str));var out=[],i,tmp=0;for(i=0;i<str.length;i++)if(tmp=tmp<<8|str.charCodeAt(i),(i&3)===3)out.push(tmp),tmp=0;if(i&3)out.push(sjcl.bitArray.partial(8*(i&3),tmp));return out}};sjcl.codec.hex={fromBits:function(arr){var out="",i;for(i=0;i<arr.length;i++)out+=((arr[i]|0)+263882790666240).toString(16).substr(4);return out.substr(0,sjcl.bitArray.bitLength(arr)/4)},toBits:function(str){var i,out=[],len;str=str.replace(/\s|0x/g,""),len=str.length,str=str+"00000000";for(i=0;i<str.length;i+=8)out.push(parseInt(str.substr(i,8),16)^0);return sjcl.bitArray.clamp(out,len*4)}};sjcl.codec.base64={_chars:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(arr,_noEquals,_url){var out="",i,bits=0,c=sjcl.codec.base64._chars,ta=0,bl=sjcl.bitArray.bitLength(arr);if(_url)c=c.substr(0,62)+"-_";for(i=0;out.length*6<bl;)if(out+=c.charAt((ta^arr[i]>>>bits)>>>26),bits<6)ta=arr[i]<<6-bits,bits+=26,i++;else ta<<=6,bits-=6;while(out.length&3&&!_noEquals)out+="=";return out},toBits:function(str,_url){str=str.replace(/\s|=/g,"");var out=[],i,bits=0,c=sjcl.codec.base64._chars,ta=0,x;if(_url)c=c.substr(0,62)+"-_";for(i=0;i<str.length;i++){if(x=c.indexOf(str.charAt(i)),x<0)throw new sjcl.exception.invalid("this isn't base64!");if(bits>26)bits-=26,out.push(ta^x>>>bits),ta=x<<32-bits;else bits+=6,ta^=x<<32-bits}if(bits&56)out.push(sjcl.bitArray.partial(bits&56,ta,1));return out}};sjcl.codec.base64url={fromBits:function(arr){return sjcl.codec.base64.fromBits(arr,1,1)},toBits:function(str){return sjcl.codec.base64.toBits(str,1)}};sjcl.codec.bytes={fromBits:function(arr){var out=[],bl=sjcl.bitArray.bitLength(arr),i,tmp;for(i=0;i<bl/8;i++){if((i&3)===0)tmp=arr[i/4];out.push(tmp>>>24),tmp<<=8}return out},toBits:function(bytes){var out=[],i,tmp=0;for(i=0;i<bytes.length;i++)if(tmp=tmp<<8|bytes[i],(i&3)===3)out.push(tmp),tmp=0;if(i&3)out.push(sjcl.bitArray.partial(8*(i&3),tmp));return out}};sjcl.hash.sha256=function(hash){if(!this._key[0])this._precompute();if(hash)this._h=hash._h.slice(0),this._buffer=hash._buffer.slice(0),this._length=hash._length;else this.reset()};sjcl.hash.sha256.hash=function(data){return new sjcl.hash.sha256().update(data).finalize()};sjcl.hash.sha256.prototype={blockSize:512,reset:function(){return this._h=this._init.slice(0),this._buffer=[],this._length=0,this},update:function(data){if(typeof data==="string")data=sjcl.codec.utf8String.toBits(data);var i,b=this._buffer=sjcl.bitArray.concat(this._buffer,data),ol=this._length,nl=this._length=ol+sjcl.bitArray.bitLength(data);if(nl>9007199254740991)throw new sjcl.exception.invalid("Cannot hash more than 2^53 - 1 bits");if(typeof Uint32Array<"u"){var c=new Uint32Array(b),j=0;for(i=512+ol-(512+ol&511);i<=nl;i+=512)this._block(c.subarray(16*j,16*(j+1))),j+=1;b.splice(0,16*j)}else for(i=512+ol-(512+ol&511);i<=nl;i+=512)this._block(b.splice(0,16));return this},finalize:function(){var i,b=this._buffer,h=this._h;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(i=b.length+2;i&15;i++)b.push(0);b.push(Math.floor(this._length/4294967296)),b.push(this._length|0);while(b.length)this._block(b.splice(0,16));return this.reset(),h},_init:[],_key:[],_precompute:function(){var i=0,prime=2,factor,isPrime;function frac(x){return(x-Math.floor(x))*4294967296|0}for(;i<64;prime++){isPrime=!0;for(factor=2;factor*factor<=prime;factor++)if(prime%factor===0){isPrime=!1;break}if(isPrime){if(i<8)this._init[i]=frac(Math.pow(prime,0.5));this._key[i]=frac(Math.pow(prime,0.3333333333333333)),i++}}},_block:function(w){var i,tmp,a,b,h=this._h,k=this._key,h0=h[0],h1=h[1],h2=h[2],h3=h[3],h4=h[4],h5=h[5],h6=h[6],h7=h[7];for(i=0;i<64;i++){if(i<16)tmp=w[i];else a=w[i+1&15],b=w[i+14&15],tmp=w[i&15]=(a>>>7^a>>>18^a>>>3^a<<25^a<<14)+(b>>>17^b>>>19^b>>>10^b<<15^b<<13)+w[i&15]+w[i+9&15]|0;tmp=tmp+h7+(h4>>>6^h4>>>11^h4>>>25^h4<<26^h4<<21^h4<<7)+(h6^h4&(h5^h6))+k[i],h7=h6,h6=h5,h5=h4,h4=h3+tmp|0,h3=h2,h2=h1,h1=h0,h0=tmp+(h1&h2^h3&(h1^h2))+(h1>>>2^h1>>>13^h1>>>22^h1<<30^h1<<19^h1<<10)|0}h[0]=h[0]+h0|0,h[1]=h[1]+h1|0,h[2]=h[2]+h2|0,h[3]=h[3]+h3|0,h[4]=h[4]+h4|0,h[5]=h[5]+h5|0,h[6]=h[6]+h6|0,h[7]=h[7]+h7|0}};sjcl.mode.ccm={name:"ccm",_progressListeners:[],listenProgress:function(cb){sjcl.mode.ccm._progressListeners.push(cb)},unListenProgress:function(cb){var index=sjcl.mode.ccm._progressListeners.indexOf(cb);if(index>-1)sjcl.mode.ccm._progressListeners.splice(index,1)},_callProgressListener:function(val){var p=sjcl.mode.ccm._progressListeners.slice(),i;for(i=0;i<p.length;i+=1)p[i](val)},encrypt:function(prf,plaintext,iv,adata,tlen){var L,out=plaintext.slice(0),tag,w=sjcl.bitArray,ivl=w.bitLength(iv)/8,ol=w.bitLength(out)/8;if(tlen=tlen||64,adata=adata||[],ivl<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(L=2;L<4&&ol>>>8*L;L++);if(L<15-ivl)L=15-ivl;return iv=w.clamp(iv,8*(15-L)),tag=sjcl.mode.ccm._computeTag(prf,plaintext,iv,adata,tlen,L),out=sjcl.mode.ccm._ctrMode(prf,out,iv,tag,tlen,L),w.concat(out.data,out.tag)},decrypt:function(prf,ciphertext,iv,adata,tlen){tlen=tlen||64,adata=adata||[];var L,w=sjcl.bitArray,ivl=w.bitLength(iv)/8,ol=w.bitLength(ciphertext),out=w.clamp(ciphertext,ol-tlen),tag=w.bitSlice(ciphertext,ol-tlen),tag2;if(ol=(ol-tlen)/8,ivl<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(L=2;L<4&&ol>>>8*L;L++);if(L<15-ivl)L=15-ivl;if(iv=w.clamp(iv,8*(15-L)),out=sjcl.mode.ccm._ctrMode(prf,out,iv,tag,tlen,L),tag2=sjcl.mode.ccm._computeTag(prf,out.data,iv,adata,tlen,L),!w.equal(out.tag,tag2))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return out.data},_macAdditionalData:function(prf,adata,iv,tlen,ol,L){var mac,tmp,i,macData=[],w=sjcl.bitArray,xor=w._xor4;if(mac=[w.partial(8,(adata.length?64:0)|tlen-2<<2|L-1)],mac=w.concat(mac,iv),mac[3]|=ol,mac=prf.encrypt(mac),adata.length){if(tmp=w.bitLength(adata)/8,tmp<=65279)macData=[w.partial(16,tmp)];else if(tmp<=4294967295)macData=w.concat([w.partial(16,65534)],[tmp]);macData=w.concat(macData,adata);for(i=0;i<macData.length;i+=4)mac=prf.encrypt(xor(mac,macData.slice(i,i+4).concat([0,0,0])))}return mac},_computeTag:function(prf,plaintext,iv,adata,tlen,L){var mac,i,w=sjcl.bitArray,xor=w._xor4;if(tlen/=8,tlen%2||tlen<4||tlen>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(adata.length>4294967295||plaintext.length>4294967295)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");mac=sjcl.mode.ccm._macAdditionalData(prf,adata,iv,tlen,w.bitLength(plaintext)/8,L);for(i=0;i<plaintext.length;i+=4)mac=prf.encrypt(xor(mac,plaintext.slice(i,i+4).concat([0,0,0])));return w.clamp(mac,tlen*8)},_ctrMode:function(prf,data,iv,tag,tlen,L){var enc,i,w=sjcl.bitArray,xor=w._xor4,ctr,l=data.length,bl=w.bitLength(data),n=l/50,p=n;if(ctr=w.concat([w.partial(8,L-1)],iv).concat([0,0,0]).slice(0,4),tag=w.bitSlice(xor(tag,prf.encrypt(ctr)),0,tlen),!l)return{tag,data:[]};for(i=0;i<l;i+=4){if(i>n)sjcl.mode.ccm._callProgressListener(i/l),n+=p;ctr[3]++,enc=prf.encrypt(ctr),data[i]^=enc[0],data[i+1]^=enc[1],data[i+2]^=enc[2],data[i+3]^=enc[3]}return{tag,data:w.clamp(data,bl)}}};sjcl.misc.hmac=function(key,Hash){this._hash=Hash=Hash||sjcl.hash.sha256;var exKey=[[],[]],i,bs=Hash.prototype.blockSize/32;if(this._baseHash=[new Hash,new Hash],key.length>bs)key=Hash.hash(key);for(i=0;i<bs;i++)exKey[0][i]=key[i]^909522486,exKey[1][i]=key[i]^1549556828;this._baseHash[0].update(exKey[0]),this._baseHash[1].update(exKey[1]),this._resultHash=new Hash(this._baseHash[0])};sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(data){if(!this._updated)return this.update(data),this.digest(data);else throw new sjcl.exception.invalid("encrypt on already updated hmac called!")};sjcl.misc.hmac.prototype.reset=function(){this._resultHash=new this._hash(this._baseHash[0]),this._updated=!1};sjcl.misc.hmac.prototype.update=function(data){this._updated=!0,this._resultHash.update(data)};sjcl.misc.hmac.prototype.digest=function(){var w=this._resultHash.finalize(),result=new this._hash(this._baseHash[1]).update(w).finalize();return this.reset(),result};sjcl.misc.pbkdf2=function(password,salt,count,length,Prff){if(count=count||1e4,length<0||count<0)throw new sjcl.exception.invalid("invalid params to pbkdf2");if(typeof password==="string")password=sjcl.codec.utf8String.toBits(password);if(typeof salt==="string")salt=sjcl.codec.utf8String.toBits(salt);Prff=Prff||sjcl.misc.hmac;var prf=new Prff(password),u,ui,i,j,k,out=[],b=sjcl.bitArray;for(k=1;32*out.length<(length||1);k++){u=ui=prf.encrypt(b.concat(salt,[k]));for(i=1;i<count;i++){ui=prf.encrypt(ui);for(j=0;j<ui.length;j++)u[j]^=ui[j]}out=out.concat(u)}if(length)out=b.clamp(out,length);return out};sjcl.prng=function(defaultParanoia){this._pools=[new sjcl.hash.sha256],this._poolEntropy=[0],this._reseedCount=0,this._robins={},this._eventId=0,this._collectorIds={},this._collectorIdNext=0,this._strength=0,this._poolStrength=0,this._nextReseed=0,this._key=[0,0,0,0,0,0,0,0],this._counter=[0,0,0,0],this._cipher=void 0,this._defaultParanoia=defaultParanoia,this._collectorsStarted=!1,this._callbacks={progress:{},seeded:{}},this._callbackI=0,this._NOT_READY=0,this._READY=1,this._REQUIRES_RESEED=2,this._MAX_WORDS_PER_BURST=65536,this._PARANOIA_LEVELS=[0,48,64,96,128,192,256,384,512,768,1024],this._MILLISECONDS_PER_RESEED=30000,this._BITS_PER_RESEED=80};sjcl.prng.prototype={randomWords:function(nwords,paranoia){var out=[],i,readiness=this.isReady(paranoia),g;if(readiness===this._NOT_READY)throw new sjcl.exception.notReady("generator isn't seeded");else if(readiness&this._REQUIRES_RESEED)this._reseedFromPools(!(readiness&this._READY));for(i=0;i<nwords;i+=4){if((i+1)%this._MAX_WORDS_PER_BURST===0)this._gate();g=this._gen4words(),out.push(g[0],g[1],g[2],g[3])}return this._gate(),out.slice(0,nwords)},setDefaultParanoia:function(paranoia,allowZeroParanoia){if(paranoia===0&&allowZeroParanoia!=="Setting paranoia=0 will ruin your security; use it only for testing")throw new sjcl.exception.invalid("Setting paranoia=0 will ruin your security; use it only for testing");this._defaultParanoia=paranoia},addEntropy:function(data,estimatedEntropy,source){source=source||"user";var id,i,tmp,t=new Date().valueOf(),robin=this._robins[source],oldReady=this.isReady(),err=0,objName;if(id=this._collectorIds[source],id===void 0)id=this._collectorIds[source]=this._collectorIdNext++;if(robin===void 0)robin=this._robins[source]=0;switch(this._robins[source]=(this._robins[source]+1)%this._pools.length,typeof data){case"number":if(estimatedEntropy===void 0)estimatedEntropy=1;this._pools[robin].update([id,this._eventId++,1,estimatedEntropy,t,1,data|0]);break;case"object":if(objName=Object.prototype.toString.call(data),objName==="[object Uint32Array]"){tmp=[];for(i=0;i<data.length;i++)tmp.push(data[i]);data=tmp}else{if(objName!=="[object Array]")err=1;for(i=0;i<data.length&&!err;i++)if(typeof data[i]!=="number")err=1}if(!err){if(estimatedEntropy===void 0){estimatedEntropy=0;for(i=0;i<data.length;i++){tmp=data[i];while(tmp>0)estimatedEntropy++,tmp=tmp>>>1}}this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data))}break;case"string":if(estimatedEntropy===void 0)estimatedEntropy=data.length;this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]),this._pools[robin].update(data);break;default:err=1}if(err)throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string");if(this._poolEntropy[robin]+=estimatedEntropy,this._poolStrength+=estimatedEntropy,oldReady===this._NOT_READY){if(this.isReady()!==this._NOT_READY)this._fireEvent("seeded",Math.max(this._strength,this._poolStrength));this._fireEvent("progress",this.getProgress())}},isReady:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia!==void 0?paranoia:this._defaultParanoia];if(this._strength&&this._strength>=entropyRequired)return this._poolEntropy[0]>this._BITS_PER_RESEED&&new Date().valueOf()>this._nextReseed?this._REQUIRES_RESEED|this._READY:this._READY;else return this._poolStrength>=entropyRequired?this._REQUIRES_RESEED|this._NOT_READY:this._NOT_READY},getProgress:function(paranoia){var entropyRequired=this._PARANOIA_LEVELS[paranoia?paranoia:this._defaultParanoia];if(this._strength>=entropyRequired)return 1;else return this._poolStrength>entropyRequired?1:this._poolStrength/entropyRequired},startCollectors:function(){if(this._collectorsStarted)return;if(this._eventListener={loadTimeCollector:this._bind(this._loadTimeCollector),mouseCollector:this._bind(this._mouseCollector),keyboardCollector:this._bind(this._keyboardCollector),accelerometerCollector:this._bind(this._accelerometerCollector),touchCollector:this._bind(this._touchCollector)},window.addEventListener)window.addEventListener("load",this._eventListener.loadTimeCollector,!1),window.addEventListener("mousemove",this._eventListener.mouseCollector,!1),window.addEventListener("keypress",this._eventListener.keyboardCollector,!1),window.addEventListener("devicemotion",this._eventListener.accelerometerCollector,!1),window.addEventListener("touchmove",this._eventListener.touchCollector,!1);else if(document.attachEvent)document.attachEvent("onload",this._eventListener.loadTimeCollector),document.attachEvent("onmousemove",this._eventListener.mouseCollector),document.attachEvent("keypress",this._eventListener.keyboardCollector);else throw new sjcl.exception.bug("can't attach event");this._collectorsStarted=!0},stopCollectors:function(){if(!this._collectorsStarted)return;if(window.removeEventListener)window.removeEventListener("load",this._eventListener.loadTimeCollector,!1),window.removeEventListener("mousemove",this._eventListener.mouseCollector,!1),window.removeEventListener("keypress",this._eventListener.keyboardCollector,!1),window.removeEventListener("devicemotion",this._eventListener.accelerometerCollector,!1),window.removeEventListener("touchmove",this._eventListener.touchCollector,!1);else if(document.detachEvent)document.detachEvent("onload",this._eventListener.loadTimeCollector),document.detachEvent("onmousemove",this._eventListener.mouseCollector),document.detachEvent("keypress",this._eventListener.keyboardCollector);this._collectorsStarted=!1},addEventListener:function(name,callback){this._callbacks[name][this._callbackI++]=callback},removeEventListener:function(name,cb){var i,j,cbs=this._callbacks[name],jsTemp=[];for(j in cbs)if(cbs.hasOwnProperty(j)&&cbs[j]===cb)jsTemp.push(j);for(i=0;i<jsTemp.length;i++)j=jsTemp[i],delete cbs[j]},_bind:function(func){var that=this;return function(){func.apply(that,arguments)}},_gen4words:function(){for(var i=0;i<4;i++)if(this._counter[i]=this._counter[i]+1|0,this._counter[i])break;return this._cipher.encrypt(this._counter)},_gate:function(){this._key=this._gen4words().concat(this._gen4words()),this._cipher=new sjcl.cipher.aes(this._key)},_reseed:function(seedWords){this._key=sjcl.hash.sha256.hash(this._key.concat(seedWords)),this._cipher=new sjcl.cipher.aes(this._key);for(var i=0;i<4;i++)if(this._counter[i]=this._counter[i]+1|0,this._counter[i])break},_reseedFromPools:function(full){var reseedData=[],strength=0,i;this._nextReseed=reseedData[0]=new Date().valueOf()+this._MILLISECONDS_PER_RESEED;for(i=0;i<16;i++)reseedData.push(Math.random()*4294967296|0);for(i=0;i<this._pools.length;i++)if(reseedData=reseedData.concat(this._pools[i].finalize()),strength+=this._poolEntropy[i],this._poolEntropy[i]=0,!full&&this._reseedCount&1<<i)break;if(this._reseedCount>=1<<this._pools.length)this._pools.push(new sjcl.hash.sha256),this._poolEntropy.push(0);if(this._poolStrength-=strength,strength>this._strength)this._strength=strength;this._reseedCount++,this._reseed(reseedData)},_keyboardCollector:function(){this._addCurrentTimeToEntropy(1)},_mouseCollector:function(ev){var x,y;try{x=ev.x||ev.clientX||ev.offsetX||0,y=ev.y||ev.clientY||ev.offsetY||0}catch(err){x=0,y=0}if(x!=0&&y!=0)this.addEntropy([x,y],2,"mouse");this._addCurrentTimeToEntropy(0)},_touchCollector:function(ev){var touch=ev.touches[0]||ev.changedTouches[0],x=touch.pageX||touch.clientX,y=touch.pageY||touch.clientY;this.addEntropy([x,y],1,"touch"),this._addCurrentTimeToEntropy(0)},_loadTimeCollector:function(){this._addCurrentTimeToEntropy(2)},_addCurrentTimeToEntropy:function(estimatedEntropy){if(typeof window<"u"&&window.performance&&typeof window.performance.now==="function")this.addEntropy(window.performance.now(),estimatedEntropy,"loadtime");else this.addEntropy(new Date().valueOf(),estimatedEntropy,"loadtime")},_accelerometerCollector:function(ev){var ac=ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z;if(window.orientation){var or=window.orientation;if(typeof or==="number")this.addEntropy(or,1,"accelerometer")}if(ac)this.addEntropy(ac,2,"accelerometer");this._addCurrentTimeToEntropy(0)},_fireEvent:function(name,arg){var j,cbs=sjcl.random._callbacks[name],cbsTemp=[];for(j in cbs)if(cbs.hasOwnProperty(j))cbsTemp.push(cbs[j]);for(j=0;j<cbsTemp.length;j++)cbsTemp[j](arg)}};sjcl.random=new sjcl.prng(6);(function(){function getCryptoModule(){try{return __require("crypto")}catch(e){return null}}try{var buf,crypt,ab;if(typeof module_sjcl<"u"&&exports_sjcl&&(crypt=getCryptoModule())&&crypt.randomBytes)buf=crypt.randomBytes(128),buf=new Uint32Array(new Uint8Array(buf).buffer),sjcl.random.addEntropy(buf,1024,"crypto.randomBytes");else if(typeof window<"u"&&typeof Uint32Array<"u"){if(ab=new Uint32Array(32),window.crypto&&window.crypto.getRandomValues)window.crypto.getRandomValues(ab);else if(window.msCrypto&&window.msCrypto.getRandomValues)window.msCrypto.getRandomValues(ab);else return;sjcl.random.addEntropy(ab,1024,"crypto.getRandomValues")}}catch(e){if(typeof window<"u"&&window.console)console.log("There was an error collecting entropy from the browser:"),console.log(e)}})();sjcl.json={defaults:{v:1,iter:1e4,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},_encrypt:function(password,plaintext,params,rp){params=params||{},rp=rp||{};var j=sjcl.json,p=j._add({iv:sjcl.random.randomWords(4,0)},j.defaults),tmp,prp,adata;if(j._add(p,params),adata=p.adata,typeof p.salt==="string")p.salt=sjcl.codec.base64.toBits(p.salt);if(typeof p.iv==="string")p.iv=sjcl.codec.base64.toBits(p.iv);if(!sjcl.mode[p.mode]||!sjcl.cipher[p.cipher]||typeof password==="string"&&p.iter<=100||p.ts!==64&&p.ts!==96&&p.ts!==128||p.ks!==128&&p.ks!==192&&p.ks!==256||(p.iv.length<2||p.iv.length>4))throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof password==="string")tmp=sjcl.misc.cachedPbkdf2(password,p),password=tmp.key.slice(0,p.ks/32),p.salt=tmp.salt;else if(sjcl.ecc&&password instanceof sjcl.ecc.elGamal.publicKey)tmp=password.kem(),p.kemtag=tmp.tag,password=tmp.key.slice(0,p.ks/32);if(typeof plaintext==="string")plaintext=sjcl.codec.utf8String.toBits(plaintext);if(typeof adata==="string")p.adata=adata=sjcl.codec.utf8String.toBits(adata);if(prp=new sjcl.cipher[p.cipher](password),j._add(rp,p),rp.key=password,p.mode==="ccm"&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&plaintext instanceof ArrayBuffer)p.ct=sjcl.arrayBuffer.ccm.encrypt(prp,plaintext,p.iv,adata,p.ts);else p.ct=sjcl.mode[p.mode].encrypt(prp,plaintext,p.iv,adata,p.ts);return p},encrypt:function(password,plaintext,params,rp){var j=sjcl.json,p=j._encrypt.apply(j,arguments);return j.encode(p)},_decrypt:function(password,ciphertext,params,rp){params=params||{},rp=rp||{};var j=sjcl.json,p=j._add(j._add(j._add({},j.defaults),ciphertext),params,!0),ct,tmp,prp,adata=p.adata;if(typeof p.salt==="string")p.salt=sjcl.codec.base64.toBits(p.salt);if(typeof p.iv==="string")p.iv=sjcl.codec.base64.toBits(p.iv);if(!sjcl.mode[p.mode]||!sjcl.cipher[p.cipher]||typeof password==="string"&&p.iter<=100||p.ts!==64&&p.ts!==96&&p.ts!==128||p.ks!==128&&p.ks!==192&&p.ks!==256||!p.iv||(p.iv.length<2||p.iv.length>4))throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof password==="string")tmp=sjcl.misc.cachedPbkdf2(password,p),password=tmp.key.slice(0,p.ks/32),p.salt=tmp.salt;else if(sjcl.ecc&&password instanceof sjcl.ecc.elGamal.secretKey)password=password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32);if(typeof adata==="string")adata=sjcl.codec.utf8String.toBits(adata);if(prp=new sjcl.cipher[p.cipher](password),p.mode==="ccm"&&sjcl.arrayBuffer&&sjcl.arrayBuffer.ccm&&p.ct instanceof ArrayBuffer)ct=sjcl.arrayBuffer.ccm.decrypt(prp,p.ct,p.iv,p.tag,adata,p.ts);else ct=sjcl.mode[p.mode].decrypt(prp,p.ct,p.iv,adata,p.ts);if(j._add(rp,p),rp.key=password,params.raw===1)return ct;else return sjcl.codec.utf8String.fromBits(ct)},decrypt:function(password,ciphertext,params,rp){var j=sjcl.json;return j._decrypt(password,j.decode(ciphertext),params,rp)},encode:function(obj){var i,out="{",comma="";for(i in obj)if(obj.hasOwnProperty(i)){if(!i.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");switch(out+=comma+'"'+i+'":',comma=",",typeof obj[i]){case"number":case"boolean":out+=obj[i];break;case"string":out+='"'+escape(obj[i])+'"';break;case"object":out+='"'+sjcl.codec.base64.fromBits(obj[i],0)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type")}}return out+"}"},decode:function(str){if(str=str.replace(/\s/g,""),!str.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");var a=str.replace(/^\{|\}$/g,"").split(/,/),out={},i,m;for(i=0;i<a.length;i++){if(!(m=a[i].match(/^\s*(?:(["']?)([a-z][a-z0-9]*)\1)\s*:\s*(?:(-?\d+)|"([a-z0-9+\/%*_.@=\-]*)"|(true|false))$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");if(m[3]!=null)out[m[2]]=parseInt(m[3],10);else if(m[4]!=null)out[m[2]]=m[2].match(/^(ct|adata|salt|iv)$/)?sjcl.codec.base64.toBits(m[4]):unescape(m[4]);else if(m[5]!=null)out[m[2]]=m[5]==="true"}return out},_add:function(target,src,requireSame){if(target===void 0)target={};if(src===void 0)return target;var i;for(i in src)if(src.hasOwnProperty(i)){if(requireSame&&target[i]!==void 0&&target[i]!==src[i])throw new sjcl.exception.invalid("required parameter overridden");target[i]=src[i]}return target},_subtract:function(plus,minus){var out={},i;for(i in plus)if(plus.hasOwnProperty(i)&&plus[i]!==minus[i])out[i]=plus[i];return out},_filter:function(src,filter){var out={},i;for(i=0;i<filter.length;i++)if(src[filter[i]]!==void 0)out[filter[i]]=src[filter[i]];return out}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc._pbkdf2Cache={};sjcl.misc.cachedPbkdf2=function(password,obj){var cache=sjcl.misc._pbkdf2Cache,c,cp,str,salt,iter;return obj=obj||{},iter=obj.iter||1000,cp=cache[password]=cache[password]||{},c=cp[iter]=cp[iter]||{firstSalt:obj.salt&&obj.salt.length?obj.salt.slice(0):sjcl.random.randomWords(2,0)},salt=obj.salt===void 0?c.firstSalt:obj.salt,c[salt]=c[salt]||sjcl.misc.pbkdf2(password,salt,obj.iter),{key:c[salt].slice(0),salt:salt.slice(0)}};sjcl.bn=function(it){this.initWith(it)};sjcl.bn.prototype={radix:24,maxMul:8,_class:sjcl.bn,copy:function(){return new this._class(this)},initWith:function(it){var i=0,k;switch(typeof it){case"object":this.limbs=it.limbs.slice(0);break;case"number":this.limbs=[it],this.normalize();break;case"string":it=it.replace(/^0x/,""),this.limbs=[],k=this.radix/4;for(i=0;i<it.length;i+=k)this.limbs.push(parseInt(it.substring(Math.max(it.length-i-k,0),it.length-i),16));break;default:this.limbs=[0]}return this},equals:function(that){if(typeof that==="number")that=new this._class(that);var difference=0,i;this.fullReduce(),that.fullReduce();for(i=0;i<this.limbs.length||i<that.limbs.length;i++)difference|=this.getLimb(i)^that.getLimb(i);return difference===0},getLimb:function(i){return i>=this.limbs.length?0:this.limbs[i]},greaterEquals:function(that){if(typeof that==="number")that=new this._class(that);var less=0,greater=0,i,a,b;i=Math.max(this.limbs.length,that.limbs.length)-1;for(;i>=0;i--)a=this.getLimb(i),b=that.getLimb(i),greater|=b-a&~less,less|=a-b&~greater;return(greater|~less)>>>31},toString:function(){this.fullReduce();var out="",i,s,l=this.limbs;for(i=0;i<this.limbs.length;i++){s=l[i].toString(16);while(i<this.limbs.length-1&&s.length<6)s="0"+s;out=s+out}return"0x"+out},addM:function(that){if(typeof that!=="object")that=new this._class(that);var i,l=this.limbs,ll=that.limbs;for(i=l.length;i<ll.length;i++)l[i]=0;for(i=0;i<ll.length;i++)l[i]+=ll[i];return this},doubleM:function(){var i,carry=0,tmp,r=this.radix,m=this.radixMask,l=this.limbs;for(i=0;i<l.length;i++)tmp=l[i],tmp=tmp+tmp+carry,l[i]=tmp&m,carry=tmp>>r;if(carry)l.push(carry);return this},halveM:function(){var i,carry=0,tmp,r=this.radix,l=this.limbs;for(i=l.length-1;i>=0;i--)tmp=l[i],l[i]=tmp+carry>>1,carry=(tmp&1)<<r;if(!l[l.length-1])l.pop();return this},subM:function(that){if(typeof that!=="object")that=new this._class(that);var i,l=this.limbs,ll=that.limbs;for(i=l.length;i<ll.length;i++)l[i]=0;for(i=0;i<ll.length;i++)l[i]-=ll[i];return this},mod:function(that){var neg=!this.greaterEquals(new sjcl.bn(0));that=new sjcl.bn(that).normalize();var out=new sjcl.bn(this).normalize(),ci=0;if(neg)out=new sjcl.bn(0).subM(out).normalize();for(;out.greaterEquals(that);ci++)that.doubleM();if(neg)out=that.sub(out).normalize();for(;ci>0;ci--)if(that.halveM(),out.greaterEquals(that))out.subM(that).normalize();return out.trim()},inverseMod:function(p){var a=new sjcl.bn(1),b=new sjcl.bn(0),x=new sjcl.bn(this),y=new sjcl.bn(p),tmp,i,nz=1;if(!(p.limbs[0]&1))throw new sjcl.exception.invalid("inverseMod: p must be odd");do{if(x.limbs[0]&1){if(!x.greaterEquals(y))tmp=x,x=y,y=tmp,tmp=a,a=b,b=tmp;if(x.subM(y),x.normalize(),!a.greaterEquals(b))a.addM(p);a.subM(b)}if(x.halveM(),a.limbs[0]&1)a.addM(p);a.normalize(),a.halveM();for(i=nz=0;i<x.limbs.length;i++)nz|=x.limbs[i]}while(nz);if(!y.equals(1))throw new sjcl.exception.invalid("inverseMod: p and x must be relatively prime");return b},add:function(that){return this.copy().addM(that)},sub:function(that){return this.copy().subM(that)},mul:function(that){if(typeof that==="number")that=new this._class(that);else that.normalize();this.normalize();var i,j,a=this.limbs,b=that.limbs,al=a.length,bl=b.length,out=new this._class,c=out.limbs,ai,ii=this.maxMul;for(i=0;i<this.limbs.length+that.limbs.length+1;i++)c[i]=0;for(i=0;i<al;i++){ai=a[i];for(j=0;j<bl;j++)c[i+j]+=ai*b[j];if(!--ii)ii=this.maxMul,out.cnormalize()}return out.cnormalize().reduce()},square:function(){return this.mul(this)},power:function(l){l=new sjcl.bn(l).normalize().trim().limbs;var i,j,out=new this._class(1),pow=this;for(i=0;i<l.length;i++)for(j=0;j<this.radix;j++){if(l[i]&1<<j)out=out.mul(pow);if(i==l.length-1&&l[i]>>j+1==0)break;pow=pow.square()}return out},mulmod:function(that,N){return this.mod(N).mul(that.mod(N)).mod(N)},powermod:function(x,N){if(x=new sjcl.bn(x),N=new sjcl.bn(N),(N.limbs[0]&1)==1){var montOut=this.montpowermod(x,N);if(montOut!=!1)return montOut}var i,j,l=x.normalize().trim().limbs,out=new this._class(1),pow=this;for(i=0;i<l.length;i++)for(j=0;j<this.radix;j++){if(l[i]&1<<j)out=out.mulmod(pow,N);if(i==l.length-1&&l[i]>>j+1==0)break;pow=pow.mulmod(pow,N)}return out},montpowermod:function(x,N){x=new sjcl.bn(x).normalize().trim(),N=new sjcl.bn(N);var i,j,radix=this.radix,out=new this._class(1),pow=this.copy(),R,s,wind,bitsize=x.bitLength();R=new sjcl.bn({limbs:N.copy().normalize().trim().limbs.map(function(){return 0})});for(s=this.radix;s>0;s--)if((N.limbs[N.limbs.length-1]>>s&1)==1){R.limbs[R.limbs.length-1]=1<<s;break}if(bitsize==0)return this;else if(bitsize<18)wind=1;else if(bitsize<48)wind=3;else if(bitsize<144)wind=4;else if(bitsize<768)wind=5;else wind=6;var RR=R.copy(),NN=N.copy(),RP=new sjcl.bn(1),NP=new sjcl.bn(0),RT=R.copy();while(RT.greaterEquals(1))if(RT.halveM(),(RP.limbs[0]&1)==0)RP.halveM(),NP.halveM();else RP.addM(NN),RP.halveM(),NP.halveM(),NP.addM(RR);RP=RP.normalize(),NP=NP.normalize(),RR.doubleM();var R2=RR.mulmod(RR,N);if(!RR.mul(RP).sub(N.mul(NP)).equals(1))return!1;var montIn=function(c){return montMul(c,R2)},montMul=function(a,b){var k,ab,right,abBar,mask=(1<<s+1)-1;if(ab=a.mul(b),right=ab.mul(NP),right.limbs=right.limbs.slice(0,R.limbs.length),right.limbs.length==R.limbs.length)right.limbs[R.limbs.length-1]&=mask;right=right.mul(N),abBar=ab.add(right).normalize().trim(),abBar.limbs=abBar.limbs.slice(R.limbs.length-1);for(k=0;k<abBar.limbs.length;k++){if(k>0)abBar.limbs[k-1]|=(abBar.limbs[k]&mask)<<radix-s-1;abBar.limbs[k]=abBar.limbs[k]>>s+1}if(abBar.greaterEquals(N))abBar.subM(N);return abBar},montOut=function(c){return montMul(c,1)};pow=montIn(pow),out=montIn(out);var h,precomp={},cap=(1<<wind-1)-1;precomp[1]=pow.copy(),precomp[2]=montMul(pow,pow);for(h=1;h<=cap;h++)precomp[2*h+1]=montMul(precomp[2*h-1],precomp[2]);var getBit=function(exp,i){var off=i%exp.radix;return(exp.limbs[Math.floor(i/exp.radix)]&1<<off)>>off};for(i=x.bitLength()-1;i>=0;)if(getBit(x,i)==0)out=montMul(out,out),i=i-1;else{var l=i-wind+1;while(getBit(x,l)==0)l++;var indx=0;for(j=l;j<=i;j++)indx+=getBit(x,j)<<j-l,out=montMul(out,out);out=montMul(out,precomp[indx]),i=l-1}return montOut(out)},trim:function(){var l=this.limbs,p;do p=l.pop();while(l.length&&p===0);return l.push(p),this},reduce:function(){return this},fullReduce:function(){return this.normalize()},normalize:function(){var carry=0,i,pv=this.placeVal,ipv=this.ipv,l,m,limbs=this.limbs,ll=limbs.length,mask=this.radixMask;for(i=0;i<ll||carry!==0&&carry!==-1;i++)l=(limbs[i]||0)+carry,m=limbs[i]=l&mask,carry=(l-m)*ipv;if(carry===-1)limbs[i-1]-=pv;return this.trim(),this},cnormalize:function(){var carry=0,i,ipv=this.ipv,l,m,limbs=this.limbs,ll=limbs.length,mask=this.radixMask;for(i=0;i<ll-1;i++)l=limbs[i]+carry,m=limbs[i]=l&mask,carry=(l-m)*ipv;return limbs[i]+=carry,this},toBits:function(len){this.fullReduce(),len=len||this.exponent||this.bitLength();var i=Math.floor((len-1)/24),w=sjcl.bitArray,e=(len+7&-8)%this.radix||this.radix,out=[w.partial(e,this.getLimb(i))];for(i--;i>=0;i--)out=w.concat(out,[w.partial(Math.min(this.radix,len),this.getLimb(i))]),len-=this.radix;return out},bitLength:function(){this.fullReduce();var out=this.radix*(this.limbs.length-1),b=this.limbs[this.limbs.length-1];for(;b;b>>>=1)out++;return out+7&-8}};sjcl.bn.fromBits=function(bits){var Class=this,out=new Class,words=[],w=sjcl.bitArray,t=this.prototype,l=Math.min(this.bitLength||4294967296,w.bitLength(bits)),e=l%t.radix||t.radix;words[0]=w.extract(bits,0,e);for(;e<l;e+=t.radix)words.unshift(w.extract(bits,e,t.radix));return out.limbs=words,out};sjcl.bn.prototype.ipv=1/(sjcl.bn.prototype.placeVal=Math.pow(2,sjcl.bn.prototype.radix));sjcl.bn.prototype.radixMask=(1<<sjcl.bn.prototype.radix)-1;sjcl.bn.pseudoMersennePrime=function(exponent,coeff){function p(it){this.initWith(it)}var ppr=p.prototype=new sjcl.bn,i,tmp,mo;mo=ppr.modOffset=Math.ceil(tmp=exponent/ppr.radix),ppr.exponent=exponent,ppr.offset=[],ppr.factor=[],ppr.minOffset=mo,ppr.fullMask=0,ppr.fullOffset=[],ppr.fullFactor=[],ppr.modulus=p.modulus=new sjcl.bn(Math.pow(2,exponent)),ppr.fullMask=0|-Math.pow(2,exponent%ppr.radix);for(i=0;i<coeff.length;i++)ppr.offset[i]=Math.floor(coeff[i][0]/ppr.radix-tmp),ppr.fullOffset[i]=Math.floor(coeff[i][0]/ppr.radix)-mo+1,ppr.factor[i]=coeff[i][1]*Math.pow(0.5,exponent-coeff[i][0]+ppr.offset[i]*ppr.radix),ppr.fullFactor[i]=coeff[i][1]*Math.pow(0.5,exponent-coeff[i][0]+ppr.fullOffset[i]*ppr.radix),ppr.modulus.addM(new sjcl.bn(Math.pow(2,coeff[i][0])*coeff[i][1])),ppr.minOffset=Math.min(ppr.minOffset,-ppr.offset[i]);return ppr._class=p,ppr.modulus.cnormalize(),ppr.reduce=function(){var i,k,l,mo=this.modOffset,limbs=this.limbs,off=this.offset,ol=this.offset.length,fac=this.factor,ll;i=this.minOffset;while(limbs.length>mo){l=limbs.pop(),ll=limbs.length;for(k=0;k<ol;k++)limbs[ll+off[k]]-=fac[k]*l;if(i--,!i)limbs.push(0),this.cnormalize(),i=this.minOffset}return this.cnormalize(),this},ppr._strongReduce=ppr.fullMask===-1?ppr.reduce:function(){var limbs=this.limbs,i=limbs.length-1,k,l;if(this.reduce(),i===this.modOffset-1){l=limbs[i]&this.fullMask,limbs[i]-=l;for(k=0;k<this.fullOffset.length;k++)limbs[i+this.fullOffset[k]]-=this.fullFactor[k]*l;this.normalize()}},ppr.fullReduce=function(){var greater,i;this._strongReduce(),this.addM(this.modulus),this.addM(this.modulus),this.normalize(),this._strongReduce();for(i=this.limbs.length;i<this.modOffset;i++)this.limbs[i]=0;greater=this.greaterEquals(this.modulus);for(i=0;i<this.limbs.length;i++)this.limbs[i]-=this.modulus.limbs[i]*greater;return this.cnormalize(),this},ppr.inverse=function(){return this.power(this.modulus.sub(2))},p.fromBits=sjcl.bn.fromBits,p};var sbp=sjcl.bn.pseudoMersennePrime;sjcl.bn.prime={p127:sbp(127,[[0,-1]]),p25519:sbp(255,[[0,-19]]),p192k:sbp(192,[[32,-1],[12,-1],[8,-1],[7,-1],[6,-1],[3,-1],[0,-1]]),p224k:sbp(224,[[32,-1],[12,-1],[11,-1],[9,-1],[7,-1],[4,-1],[1,-1],[0,-1]]),p256k:sbp(256,[[32,-1],[9,-1],[8,-1],[7,-1],[6,-1],[4,-1],[0,-1]]),p192:sbp(192,[[0,-1],[64,-1]]),p224:sbp(224,[[0,1],[96,-1]]),p256:sbp(256,[[0,-1],[96,1],[192,1],[224,-1]]),p384:sbp(384,[[0,-1],[32,1],[96,-1],[128,-1]]),p521:sbp(521,[[0,-1]])};sjcl.bn.random=function(modulus,paranoia){if(typeof modulus!=="object")modulus=new sjcl.bn(modulus);var words,i,l=modulus.limbs.length,m=modulus.limbs[l-1]+1,out=new sjcl.bn;while(!0){do if(words=sjcl.random.randomWords(l,paranoia),words[l-1]<0)words[l-1]+=4294967296;while(Math.floor(words[l-1]/m)===Math.floor(4294967296/m));words[l-1]%=m;for(i=0;i<l-1;i++)words[i]&=modulus.radixMask;if(out.limbs=words,!out.greaterEquals(modulus))return out}};if(typeof ArrayBuffer>"u")(function(globals){globals.ArrayBuffer=function(){},globals.DataView=function(){}})(null);sjcl.codec.arrayBuffer={fromBits:function(arr,padding,padding_count){var out,i,ol,tmp,smallest;if(padding=padding==null?!0:padding,padding_count=padding_count||8,arr.length===0)return new ArrayBuffer(0);if(ol=sjcl.bitArray.bitLength(arr)/8,sjcl.bitArray.bitLength(arr)%8!==0)throw new sjcl.exception.invalid("Invalid bit size, must be divisble by 8 to fit in an arraybuffer correctly");if(padding&&ol%padding_count!==0)ol+=padding_count-ol%padding_count;tmp=new DataView(new ArrayBuffer(arr.length*4));for(i=0;i<arr.length;i++)tmp.setUint32(i*4,arr[i]<<32);if(out=new DataView(new ArrayBuffer(ol)),out.byteLength===tmp.byteLength)return tmp.buffer;smallest=tmp.byteLength<out.byteLength?tmp.byteLength:out.byteLength;for(i=0;i<smallest;i++)out.setUint8(i,tmp.getUint8(i));return out.buffer},toBits:function(buffer){var i,out=[],len,inView,tmp;if(buffer.byteLength===0)return[];inView=new DataView(buffer),len=inView.byteLength-inView.byteLength%4;for(var i=0;i<len;i+=4)out.push(inView.getUint32(i));if(inView.byteLength%4!=0){tmp=new DataView(new ArrayBuffer(4));for(var i=0,l=inView.byteLength%4;i<l;i++)tmp.setUint8(i+4-l,inView.getUint8(len+i));out.push(sjcl.bitArray.partial(inView.byteLength%4*8,tmp.getUint32(0)))}return out},hexDumpBuffer:function(buffer){var stringBufferView=new DataView(buffer),string="",pad=function(n,width){return n=n+"",n.length>=width?n:Array(width-n.length+1).join("0")+n};for(var i=0;i<stringBufferView.byteLength;i+=2){if(i%16==0)string+=`
|
|
116
|
+
`+i.toString(16)+"\t";string+=pad(stringBufferView.getUint16(i).toString(16),4)+" "}if(typeof console===void 0)console=console||{log:function(){}};console.log(string.toUpperCase())}};var sjcl_default=sjcl;function assertNever(name,x){throw Error(`unexpected ${name} identifier: ${x}`)}function getHashParams(hash){switch(hash){case"SHA-1":return{name:hash,hLen:20};case"SHA-256":return{name:hash,hLen:32};case"SHA-384":return{name:hash,hLen:48};case"SHA-512":return{name:hash,hLen:64};default:assertNever("Hash",hash)}}function os2ip(bytes){return sjcl_default.bn.fromBits(sjcl_default.codec.bytes.toBits(Array.from(bytes)))}function i2osp(num,byteLength){if(Math.ceil(num.bitLength()/8)>byteLength)throw Error(`number does not fit in ${byteLength} bytes`);let bytes=new Uint8Array(byteLength),unpadded=new Uint8Array(sjcl_default.codec.bytes.fromBits(num.toBits(void 0)));return bytes.set(unpadded,byteLength-unpadded.length),bytes}function joinAll(a){let size=0;for(let ai of a)size+=ai.length;let ret=new Uint8Array(new ArrayBuffer(size)),offset=0;for(let ai of a)ret.set(ai,offset),offset+=ai.length;return ret}function xor(a,b){if(a.length!==b.length||a.length===0)throw Error(`arrays of different length: ${a.length} - ${b.length}`);let ai=a[Symbol.iterator](),bi=b[Symbol.iterator]();return new Uint8Array(a.length).map(()=>ai.next().value^bi.next().value)}function incCounter(c){if(c[3]++,c[3]!=0)return;if(c[2]++,c[2]!=0)return;if(c[1]++,c[1]!=0)return;c[0]++;return}async function mgf1(h,seed,mLen){let n=Math.ceil(mLen/h.hLen);if(n>Math.pow(2,32))throw Error("mask too long");let T=new Uint8Array,counter=new Uint8Array(4);for(let i=0;i<n;i++){let hash=new Uint8Array(await crypto.subtle.digest(h.name,joinAll([seed,counter]).slice().buffer));T=joinAll([T,hash]),incCounter(counter)}return T.subarray(0,mLen)}async function emsa_pss_encode(msg,emBits,opts,mgf=mgf1){let{hash,sLen}=opts,hashParams=getHashParams(hash),{hLen}=hashParams,emLen=Math.ceil(emBits/8),mHash=new Uint8Array(await crypto.subtle.digest(hash,msg.slice().buffer));if(emLen<hLen+sLen+2)throw Error("encoding error");let salt=crypto.getRandomValues(new Uint8Array(sLen)),mPrime=joinAll([new Uint8Array(8),mHash,salt]),h=new Uint8Array(await crypto.subtle.digest(hash,mPrime.slice().buffer)),ps=new Uint8Array(emLen-sLen-hLen-2),db=joinAll([ps,Uint8Array.of(1),salt]),dbMask=await mgf(hashParams,h,emLen-hLen-1),maskedDB=xor(db,dbMask);return maskedDB[0]&=255>>8*emLen-emBits,joinAll([maskedDB,h,Uint8Array.of(188)])}function rsavp1(pkS,s){if(!s.greaterEquals(new sjcl_default.bn(0))||s.greaterEquals(pkS.n))throw Error("signature representative out of range");return s.powermod(pkS.e,pkS.n)}function rsasp1(skS,m){if(!m.greaterEquals(new sjcl_default.bn(0))||m.greaterEquals(skS.n))throw Error("signature representative out of range");return m.powermod(skS.d,skS.n)}function is_coprime(x,n){try{x.inverseMod(n)}catch{return!1}return!0}function random_integer_uniform(n,kLen){for(let i=0;i<128;i++){let r=os2ip(crypto.getRandomValues(new Uint8Array(kLen)));if(!(r.greaterEquals(n)||r.equals(0)))return r}throw Error("reached maximum tries for random integer generation")}var NATIVE_SUPPORT_NAME="RSA-RAW";async function rsaRawBlingSign(privateKey,blindMsg){if(privateKey.algorithm.name!==NATIVE_SUPPORT_NAME)privateKey=await crypto.subtle.importKey("pkcs8",await crypto.subtle.exportKey("pkcs8",privateKey),{...privateKey.algorithm,name:NATIVE_SUPPORT_NAME},privateKey.extractable,privateKey.usages);let signature=await crypto.subtle.sign({name:privateKey.algorithm.name},privateKey,blindMsg.slice().buffer);return new Uint8Array(signature)}var PrepareType;(function(PrepareType){PrepareType[PrepareType.Deterministic=0]="Deterministic",PrepareType[PrepareType.Randomized=32]="Randomized"})(PrepareType||(PrepareType={}));class BlindRSA{params;static NAME="RSA-PSS";constructor(params){switch(this.params=params,params.prepareType){case PrepareType.Deterministic:case PrepareType.Randomized:return;default:assertNever("PrepareType",params.prepareType)}}toString(){let hash=this.params.hash.replace("-",""),pssType="PSS"+(this.params.saltLength===0?"ZERO":""),prepare=PrepareType[this.params.prepareType];return`RSABSSA-${hash}-${pssType}-${prepare}`}prepare(msg){let msg_prefix_len=this.params.prepareType,msg_prefix=crypto.getRandomValues(new Uint8Array(msg_prefix_len));return joinAll([msg_prefix,msg])}async extractKeyParams(key,type){if(key.type!==type||key.algorithm.name!==BlindRSA.NAME)throw Error(`key is not ${BlindRSA.NAME}`);if(!key.extractable)throw Error("key is not extractable");let{modulusLength:modulusLengthBits,hash:hashFn}=key.algorithm,modulusLengthBytes=Math.ceil(modulusLengthBits/8),hash=hashFn.name;if(hash.toLowerCase()!==this.params.hash.toLowerCase())throw Error(`hash is not ${this.params.hash}`);return{jwkKey:await crypto.subtle.exportKey("jwk",key),modulusLengthBits,modulusLengthBytes,hash}}async blind(publicKey,msg){let{jwkKey,modulusLengthBits:modulusLength,modulusLengthBytes:kLen,hash}=await this.extractKeyParams(publicKey,"public");if(!jwkKey.n||!jwkKey.e)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n)),pk={e:sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.e)),n},opts={sLen:this.params.saltLength,hash},encoded_msg=await emsa_pss_encode(msg,modulusLength-1,opts),m=os2ip(encoded_msg);if(!is_coprime(m,n))throw Error("invalid input");let r=random_integer_uniform(n,kLen),inv;try{inv=i2osp(r.inverseMod(n),kLen)}catch(e){throw Error(`blinding error: ${e.toString()}`)}let x=rsavp1(pk,r),z=m.mulmod(x,n);return{blindedMsg:i2osp(z,kLen),inv}}async blindSign(privateKey,blindMsg){if(this.params.supportsRSARAW)return rsaRawBlingSign(privateKey,blindMsg);let{jwkKey,modulusLengthBytes:kLen}=await this.extractKeyParams(privateKey,"private");if(!jwkKey.n||!jwkKey.d||!jwkKey.e)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n)),d=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.d)),e=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.e)),sk={n,d},pk={n,e},m=os2ip(blindMsg),s=rsasp1(sk,m),mp=rsavp1(pk,s);if(!m.equals(mp))throw Error("signing failure");return i2osp(s,kLen)}async finalize(publicKey,msg,blindSig,inv){let{jwkKey,modulusLengthBytes:kLen}=await this.extractKeyParams(publicKey,"public");if(!jwkKey.n)throw Error("key has invalid parameters");let n=sjcl_default.bn.fromBits(sjcl_default.codec.base64url.toBits(jwkKey.n));if(inv.length!=kLen)throw Error("unexpected input size");let rInv=os2ip(inv);if(blindSig.length!=kLen)throw Error("unexpected input size");let s=os2ip(blindSig).mulmod(rInv,n),sig=i2osp(s,kLen),algorithm={name:BlindRSA.NAME,saltLength:this.params.saltLength};if(!await crypto.subtle.verify(algorithm,publicKey,sig.slice().buffer,msg.slice().buffer))throw Error("invalid signature");return sig}static generateKey(algorithm){return crypto.subtle.generateKey({...algorithm,name:BlindRSA.NAME},!0,["sign","verify"])}generateKey(algorithm){return BlindRSA.generateKey({...algorithm,hash:this.params.hash})}verify(publicKey,signature,message){return crypto.subtle.verify({name:BlindRSA.NAME,saltLength:this.params.saltLength},publicKey,signature.slice().buffer,message.slice().buffer)}}var Params={RSABSSA_SHA384_PSS_Randomized:{name:"RSABSSA-SHA384-PSS-Randomized",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Randomized},RSABSSA_SHA384_PSS_Deterministic:{name:"RSABSSA-SHA384-PSS-Deterministic",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Deterministic},RSABSSA_SHA384_PSSZERO_Randomized:{name:"RSABSSA-SHA384-PSSZERO-Randomized",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Randomized},RSABSSA_SHA384_PSSZERO_Deterministic:{name:"RSABSSA-SHA384-PSSZERO-Deterministic",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Deterministic},RSAPBSSA_SHA384_PSS_Randomized:{name:"RSAPBSSA-SHA384-PSS-Randomized",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Randomized},RSAPBSSA_SHA384_PSS_Deterministic:{name:"RSAPBSSA-SHA384-PSS-Deterministic",hash:"SHA-384",saltLength:48,prepareType:PrepareType.Deterministic},RSAPBSSA_SHA384_PSSZERO_Randomized:{name:"RSAPBSSA-SHA384-PSSZERO-Randomized",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Randomized},RSAPBSSA_SHA384_PSSZERO_Deterministic:{name:"RSAPBSSA-SHA384-PSSZERO-Deterministic",hash:"SHA-384",saltLength:0,prepareType:PrepareType.Deterministic}},RSABSSA={SHA384:{generateKey:(algorithm)=>BlindRSA.generateKey({...algorithm,hash:"SHA-384"}),PSS:{Randomized:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSS_Randomized,...params}),Deterministic:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSS_Deterministic,...params})},PSSZero:{Randomized:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSSZERO_Randomized,...params}),Deterministic:(params={supportsRSARAW:!1})=>new BlindRSA({...Params.RSABSSA_SHA384_PSSZERO_Deterministic,...params})}}};var PINNED_ISSUER_KEYS={"https://api.velloo.ai":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs54VddkMfc1b0S+2i8GOouyseMo4mb6+zPYBv/Omu1sZQfTYFVFttZOWO0vw6ObT8EVBC/4qCgZPa1d8To55rNe6ZSWJkvXFuoEeOaVDlfDhxduFnVFa5QelxEwVXAFpVSSdPwcFicoCNxuhHTbezPQl3yKLZVwIUncMNhpwsjXVyrKEtSp+5cAY1SH2axLBpqPT1nReH5HAZmAmUcZFvJND45htig2o2tvFSTkOCurFt6lw9HiCWjNE1L1C3Y/VuGkvIVbQShNipyBvgn1ZtJxFvkY4M4ULUj9G9JMb6pYtUupUddPLfpROXSPFvbDk22JOOP2VWtv5r9mXeTCAIQIDAQAB"};var TOKEN_SCHEME="RSABSSA-SHA384.PSS.Randomized",suite=RSABSSA.SHA384.PSS.Randomized(),KEY_ALG={name:"RSA-PSS",hash:"SHA-384"},BATCH=10,defaultTokenStorePath=()=>join15(homedir3(),".velloo","feedback-tokens.json");async function loadStore(path){try{return JSON.parse(await readFile6(path,"utf8"))}catch{return{}}}async function saveStore(path,store){await mkdir8(dirname10(path),{recursive:!0}),await writeJsonAtomic(path,store)}var originOf=(url)=>new URL(url).origin;async function fetchIssuerKey(cloudUrl,prior){let res=await fetch(`${cloudUrl}/v1/feedback/token-key`,{signal:AbortSignal.timeout(5000)});if(!res.ok)throw new TokenIssueError("rejected",`token key fetch failed (${res.status})`);let parsed=FeedbackIssuerResponseSchema.safeParse(await res.json().catch(()=>null));if(!parsed.success)throw Error("the cloud's feedback token key reply was malformed");let{scheme,publicKey}=parsed.data;if(scheme!==TOKEN_SCHEME)throw Error(`unsupported feedback token scheme ${JSON.stringify(scheme)}`);let pinned=PINNED_ISSUER_KEYS[originOf(cloudUrl)];if(pinned&&publicKey!==pinned)throw Error("the cloud's feedback issuer key does not match the key pinned in this velloo build \u2014 refusing to fetch tokens (a per-user key would deanonymize feedback).");if(!pinned&&prior&&prior.publicKey!==publicKey)throw Error(`the cloud's feedback issuer key changed since tokens were first issued \u2014 refusing to fetch more. If the rotation is expected, delete ${defaultTokenStorePath()} and retry.`);return{publicKey:await crypto.subtle.importKey("spki",Buffer.from(publicKey,"base64"),KEY_ALG,!0,["verify"]),spkiB64:publicKey}}async function topUpTokens(cloud,min=1,storePath=defaultTokenStorePath()){let store=await loadStore(storePath),origin=originOf(cloud.url),entry=store[origin];if((entry?.tokens.length??0)>=min)return;if(!cloud.token)throw new TokenIssueError("signed-out","not signed in \u2014 run `velloo login` to fetch feedback tokens");let issuer=await fetchIssuerKey(cloud.url,entry),blinds=[];for(let i=0;i<BATCH;i++){let prepared=suite.prepare(crypto.getRandomValues(new Uint8Array(32)));blinds.push({prepared,...await suite.blind(issuer.publicKey,prepared)})}let res=await fetch(`${cloud.url}/v1/feedback/tokens`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${cloud.token}`},body:JSON.stringify({blinded:blinds.map((b)=>Buffer.from(b.blindedMsg).toString("base64"))}),signal:AbortSignal.timeout(15000)});if(!res.ok)throw new TokenIssueError(res.status===402||res.status===429?"no-tokens":"rejected",`token issuance failed (${res.status})`);let issued=FeedbackSignaturesResponseSchema.safeParse(await res.json().catch(()=>null));if(!issued.success||issued.data.signatures.length!==blinds.length)throw Error("token issuance returned a malformed signature batch");let{signatures}=issued.data,tokens=[];for(let i=0;i<blinds.length;i++){let b=blinds[i];if(!b)continue;let sig=await suite.finalize(issuer.publicKey,b.prepared,Buffer.from(signatures[i]??"","base64"),b.inv);if(!await suite.verify(issuer.publicKey,sig,b.prepared))throw Error("issued token failed local verification \u2014 not storing it");tokens.push({token:Buffer.from(b.prepared).toString("base64"),sig:Buffer.from(sig).toString("base64")})}store[origin]={scheme:TOKEN_SCHEME,publicKey:issuer.spkiB64,tokens:[...entry?.tokens??[],...tokens]},await saveStore(storePath,store)}async function popToken(cloudUrl,storePath){let store=await loadStore(storePath),entry=store[originOf(cloudUrl)],token=entry?.tokens.shift();if(!entry||!token)return null;return await saveStore(storePath,store),token}function feedbackError(reason,message,retryable){return{kind:"FeedbackNotSent",reason,message,retryable}}class TokenIssueError extends Error{reason;constructor(reason,message){super(message);this.reason=reason}}async function sendAnonymousFeedback(cloud,payload,storePath=defaultTokenStorePath()){for(let attempt=0;attempt<2;attempt++){try{await topUpTokens(cloud,1,storePath)}catch(e){let message=e instanceof Error?e.message:String(e);if(e instanceof TokenIssueError)return err(feedbackError(e.reason,message,e.reason!=="signed-out"));return err(feedbackError("unreachable",message,!0))}let token=await popToken(cloud.url,storePath);if(!token)return err(feedbackError("no-tokens","no anonymous feedback tokens available for this account",!0));try{let res=await fetch(`${cloud.url}/v1/feedback/anonymous`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({body:payload.body,...payload.toolVersion?{toolVersion:payload.toolVersion}:{},...payload.source?{source:payload.source}:{},token:token.token,signature:token.sig}),signal:AbortSignal.timeout(5000)});if(res.status===409)continue;if(!res.ok)return err(feedbackError("rejected",`velloo-cloud refused the message (${res.status}).`,!1));return ok("Thanks \u2014 your feedback was sent (anonymously).")}catch{return err(feedbackError("unreachable","Couldn't reach velloo-cloud; feedback not sent.",!0))}}return err(feedbackError("rejected","feedback tokens kept colliding \u2014 try again later",!0))}import{existsSync as existsSync6}from"fs";import{isAbsolute,resolve as resolve6}from"path";function createServerProviderLoader(folderRoot,hostApp){let hostAppRoot=folderRoot?hostAppRootFrom(folderRoot,hostApp):void 0;return createProviderLoader({none:async()=>(await import("./chunk-eyr8hrzf.js")).createProvider(),mui:async()=>(await import("./chunk-ydhwv29b.js")).createProvider(),antd:async()=>(await import("./chunk-v40fn08f.js")).createProvider(),chakra:async()=>(await import("./chunk-jr0ss0mm.js")).createProvider(),"shadcn-upstream":async(library)=>{let cacheDir=resolveUpstreamCacheDir(library,folderRoot);return(await import("./chunk-2c7jbx46.js")).createProvider({version:library.version,...cacheDir?{cacheDir}:{},...hostAppRoot?{hostAppRoot}:{}})}})}function resolveUpstreamCacheDir(library,folderRoot){let path=library.componentsPath;if(!path||path==="binary")return null;if(path.startsWith("~/")){let home=process.env.HOME??process.env.USERPROFILE??"";if(!home)return null;let abs=resolve6(home,path.slice(2));return existsSync6(abs)?abs:null}if(isAbsolute(path))return library.source==="in-repo"||existsSync6(path)?path:null;if(!folderRoot)return null;let abs=resolveAppPath(folderRoot,path);return library.source==="in-repo"||existsSync6(abs)?abs:null}async function resolveProviders(config,folderRoot,loader){let effective=loader??createServerProviderLoader(folderRoot,config.hostApp),providers={};for(let[libraryId,library]of Object.entries(config.libraries))try{providers[libraryId]=await effective(library)}catch(err){if(err instanceof UnknownProviderError)throw Error(`velloo: this binary doesn't know how to load library "${err.id}" (registered as "${libraryId}"). Upgrade velloo or pick a supported provider in .design/config.json.`);throw err}let defaultProvider=providers[config.defaultLibrary];if(!defaultProvider)throw Error(`velloo: defaultLibrary "${config.defaultLibrary}" doesn't match any registered library.`);if(config.styling){let css=config.styling.framework,wanted=CSS_FRAMEWORK_CHANNEL[css];if(!Object.values(providers).some((p)=>styleChannelOf(p,css).kind===wanted)){let libs=Object.values(config.libraries).map((l)=>l.id).join(", ");throw Error(`velloo: config.styling.framework "${css}" isn't supported by any registered library (${libs}). shadcn and MUI carry their own styling; only the no-framework library can pair with "none". Fix the CSS framework or library in .design/config.json.`)}}return{providers,defaultProvider}}import{existsSync as existsSync7}from"fs";import{resolve as resolve7}from"path";var CONFIG_NAMES=["tailwind.config.ts","tailwind.config.js","tailwind.config.cjs","tailwind.config.mjs"];function findHostTailwindConfig(folderRoot,hostApp){let root=hostAppRootFrom(folderRoot,hostApp);for(let name of CONFIG_NAMES){let path=resolve7(root,name);if(existsSync7(path))return path}return null}import{readFile as readFile7}from"fs/promises";import{dirname as dirname11,join as join16}from"path";import{compile}from"@tailwindcss/node";import{Scanner}from"@tailwindcss/oxide";class TailwindJit{pagesDir;extraEntryCss;extraSourceDirs;hostConfigPath;compilerPromise=null;cached=null;cachedCandidates=null;snippetsDir;providers;tailwindProviders;constructor(providers,pagesDir,snippetsDir,extraEntryCss,extraSourceDirs,hostConfigPath,folderCss){this.pagesDir=pagesDir;this.extraEntryCss=extraEntryCss;this.extraSourceDirs=extraSourceDirs;this.hostConfigPath=hostConfigPath;if(this.providers=Array.isArray(providers)?providers:[providers],this.providers.length===0)throw Error("TailwindJit: at least one provider is required.");this.tailwindProviders=this.providers.filter((p)=>styleChannelOf(p,folderCss).needsTailwindJit),this.snippetsDir=snippetsDir??join16(pagesDir,"..","snippets")}invalidate(){this.cached=null,this.cachedCandidates=null,this.compilerPromise=null}async build(extraCandidates){if(this.tailwindProviders.length===0)return"";let hasExtra=extraCandidates!==void 0&&extraCandidates.length>0;if(!hasExtra&&this.cached!==null)return this.cached;let compiler=await this.getCompiler(),candidates=this.scanCandidates();if(!hasExtra)return this.cached=compiler.build(candidates),this.cached;return compiler.build([...candidates,...extraCandidates])}scanCandidates(){if(this.cachedCandidates!==null)return this.cachedCandidates;let dedupedDirs=Array.from(new Set([...this.tailwindProviders.map((p)=>p.componentsDir),helpersComponentsDir])),hostDirs=Array.from(new Set(this.extraSourceDirs?.()??[])),scanner=new Scanner({sources:[...dedupedDirs.map((base)=>({base,pattern:"**/*.tsx",negated:!1})),...hostDirs.map((base)=>({base,pattern:"**/*.tsx",negated:!1})),{base:this.pagesDir,pattern:"**/*.json",negated:!1},{base:this.snippetsDir,pattern:"**/*.json",negated:!1}]});return this.cachedCandidates=scanner.scan(),this.cachedCandidates}entryCss(){return this.mergedEntryCss()}async mergedEntryCss(){let primary=this.tailwindProviders[0];if(!primary)throw Error("TailwindJit: no Tailwind-channel provider, cannot build entry CSS.");let primaryBase=dirname11(primary.styleEntryPath),extra=this.extraEntryCss?.()??"";if(this.tailwindProviders.length===1){let css=await readFile7(primary.styleEntryPath,"utf8");return{css:extra?`${css}
|
|
117
117
|
|
|
118
118
|
${extra}`:css,base:primaryBase}}let merged=(await Promise.all(this.tailwindProviders.map(async(p)=>{let css=await readFile7(p.styleEntryPath,"utf8");return`/* === provider: ${p.id} (${p.version}) === */
|
|
119
119
|
${css}`}))).join(`
|
|
@@ -130,11 +130,11 @@ ${extra}`:merged,base:primaryBase}}warnedHostConfig=!1;getCompiler(){if(this.com
|
|
|
130
130
|
`:`[${source[i]}\r
|
|
131
131
|
]`;continue}if(pattern+=source[i],source[i]==="\\")isEscaped=!0;else if(inCharGroup&&source[i]==="]")inCharGroup=!1;else if(!inCharGroup&&source[i]==="[")inCharGroup=!0}try{new RegExp(pattern)}catch{return console.warn(`Could not convert regex pattern at ${refs.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`),regex.source}return pattern}function parseRecordDef(def,refs){if(refs.target==="openAi")console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead.");if(refs.target==="openApi3"&&def.keyType?._def.typeName===ZodFirstPartyTypeKind2.ZodEnum)return{type:"object",required:def.keyType._def.values,properties:def.keyType._def.values.reduce((acc,key)=>({...acc,[key]:parseDef(def.valueType._def,{...refs,currentPath:[...refs.currentPath,"properties",key]})??parseAnyDef(refs)}),{}),additionalProperties:refs.rejectedAdditionalProperties};let schema={type:"object",additionalProperties:parseDef(def.valueType._def,{...refs,currentPath:[...refs.currentPath,"additionalProperties"]})??refs.allowedAdditionalProperties};if(refs.target==="openApi3")return schema;if(def.keyType?._def.typeName===ZodFirstPartyTypeKind2.ZodString&&def.keyType._def.checks?.length){let{type,...keyType}=parseStringDef(def.keyType._def,refs);return{...schema,propertyNames:keyType}}else if(def.keyType?._def.typeName===ZodFirstPartyTypeKind2.ZodEnum)return{...schema,propertyNames:{enum:def.keyType._def.values}};else if(def.keyType?._def.typeName===ZodFirstPartyTypeKind2.ZodBranded&&def.keyType._def.type._def.typeName===ZodFirstPartyTypeKind2.ZodString&&def.keyType._def.type._def.checks?.length){let{type,...keyType}=parseBrandedDef(def.keyType._def,refs);return{...schema,propertyNames:keyType}}return schema}function parseMapDef(def,refs){if(refs.mapStrategy==="record")return parseRecordDef(def,refs);let keys=parseDef(def.keyType._def,{...refs,currentPath:[...refs.currentPath,"items","items","0"]})||parseAnyDef(refs),values=parseDef(def.valueType._def,{...refs,currentPath:[...refs.currentPath,"items","items","1"]})||parseAnyDef(refs);return{type:"array",maxItems:125,items:{type:"array",items:[keys,values],minItems:2,maxItems:2}}}function parseNativeEnumDef(def){let object=def.values,actualValues=Object.keys(def.values).filter((key)=>typeof object[object[key]]!=="number").map((key)=>object[key]),parsedTypes=Array.from(new Set(actualValues.map((values)=>typeof values)));return{type:parsedTypes.length===1?parsedTypes[0]==="string"?"string":"number":["string","number"],enum:actualValues}}function parseNeverDef(refs){return refs.target==="openAi"?void 0:{not:parseAnyDef({...refs,currentPath:[...refs.currentPath,"not"]})}}function parseNullDef(refs){return refs.target==="openApi3"?{enum:["null"],nullable:!0}:{type:"null"}}var primitiveMappings={ZodString:"string",ZodNumber:"number",ZodBigInt:"integer",ZodBoolean:"boolean",ZodNull:"null"};function parseUnionDef(def,refs){if(refs.target==="openApi3")return asAnyOf(def,refs);let options=def.options instanceof Map?Array.from(def.options.values()):def.options;if(options.every((x)=>(x._def.typeName in primitiveMappings)&&(!x._def.checks||!x._def.checks.length))){let types=options.reduce((types,x)=>{let type=primitiveMappings[x._def.typeName];return type&&!types.includes(type)?[...types,type]:types},[]);return{type:types.length>1?types:types[0]}}else if(options.every((x)=>x._def.typeName==="ZodLiteral"&&!x.description)){let types=options.reduce((acc,x)=>{let type=typeof x._def.value;switch(type){case"string":case"number":case"boolean":return[...acc,type];case"bigint":return[...acc,"integer"];case"object":if(x._def.value===null)return[...acc,"null"];case"symbol":case"undefined":case"function":default:return acc}},[]);if(types.length===options.length){let uniqueTypes=types.filter((x,i,a)=>a.indexOf(x)===i);return{type:uniqueTypes.length>1?uniqueTypes:uniqueTypes[0],enum:options.reduce((acc,x)=>acc.includes(x._def.value)?acc:[...acc,x._def.value],[])}}}else if(options.every((x)=>x._def.typeName==="ZodEnum"))return{type:"string",enum:options.reduce((acc,x)=>[...acc,...x._def.values.filter((x)=>!acc.includes(x))],[])};return asAnyOf(def,refs)}var asAnyOf=(def,refs)=>{let anyOf=(def.options instanceof Map?Array.from(def.options.values()):def.options).map((x,i)=>parseDef(x._def,{...refs,currentPath:[...refs.currentPath,"anyOf",`${i}`]})).filter((x)=>!!x&&(!refs.strictUnions||typeof x==="object"&&Object.keys(x).length>0));return anyOf.length?{anyOf}:void 0};function parseNullableDef(def,refs){if(["ZodString","ZodNumber","ZodBigInt","ZodBoolean","ZodNull"].includes(def.innerType._def.typeName)&&(!def.innerType._def.checks||!def.innerType._def.checks.length)){if(refs.target==="openApi3")return{type:primitiveMappings[def.innerType._def.typeName],nullable:!0};return{type:[primitiveMappings[def.innerType._def.typeName],"null"]}}if(refs.target==="openApi3"){let base=parseDef(def.innerType._def,{...refs,currentPath:[...refs.currentPath]});if(base&&"$ref"in base)return{allOf:[base],nullable:!0};return base&&{...base,nullable:!0}}let base=parseDef(def.innerType._def,{...refs,currentPath:[...refs.currentPath,"anyOf","0"]});return base&&{anyOf:[base,{type:"null"}]}}function parseNumberDef(def,refs){let res={type:"number"};if(!def.checks)return res;for(let check of def.checks)switch(check.kind){case"int":res.type="integer",addErrorMessage(res,"type",check.message,refs);break;case"min":if(refs.target==="jsonSchema7")if(check.inclusive)setResponseValueAndErrors(res,"minimum",check.value,check.message,refs);else setResponseValueAndErrors(res,"exclusiveMinimum",check.value,check.message,refs);else{if(!check.inclusive)res.exclusiveMinimum=!0;setResponseValueAndErrors(res,"minimum",check.value,check.message,refs)}break;case"max":if(refs.target==="jsonSchema7")if(check.inclusive)setResponseValueAndErrors(res,"maximum",check.value,check.message,refs);else setResponseValueAndErrors(res,"exclusiveMaximum",check.value,check.message,refs);else{if(!check.inclusive)res.exclusiveMaximum=!0;setResponseValueAndErrors(res,"maximum",check.value,check.message,refs)}break;case"multipleOf":setResponseValueAndErrors(res,"multipleOf",check.value,check.message,refs);break}return res}function parseObjectDef(def,refs){let forceOptionalIntoNullable=refs.target==="openAi",result={type:"object",properties:{}},required=[],shape=def.shape();for(let propName in shape){let propDef=shape[propName];if(propDef===void 0||propDef._def===void 0)continue;let propOptional=safeIsOptional(propDef);if(propOptional&&forceOptionalIntoNullable){if(propDef._def.typeName==="ZodOptional")propDef=propDef._def.innerType;if(!propDef.isNullable())propDef=propDef.nullable();propOptional=!1}let parsedDef=parseDef(propDef._def,{...refs,currentPath:[...refs.currentPath,"properties",propName],propertyPath:[...refs.currentPath,"properties",propName]});if(parsedDef===void 0)continue;if(result.properties[propName]=parsedDef,!propOptional)required.push(propName)}if(required.length)result.required=required;let additionalProperties=decideAdditionalProperties(def,refs);if(additionalProperties!==void 0)result.additionalProperties=additionalProperties;return result}function decideAdditionalProperties(def,refs){if(def.catchall._def.typeName!=="ZodNever")return parseDef(def.catchall._def,{...refs,currentPath:[...refs.currentPath,"additionalProperties"]});switch(def.unknownKeys){case"passthrough":return refs.allowedAdditionalProperties;case"strict":return refs.rejectedAdditionalProperties;case"strip":return refs.removeAdditionalStrategy==="strict"?refs.allowedAdditionalProperties:refs.rejectedAdditionalProperties}}function safeIsOptional(schema){try{return schema.isOptional()}catch{return!0}}var parseOptionalDef=(def,refs)=>{if(refs.currentPath.toString()===refs.propertyPath?.toString())return parseDef(def.innerType._def,refs);let innerSchema=parseDef(def.innerType._def,{...refs,currentPath:[...refs.currentPath,"anyOf","1"]});return innerSchema?{anyOf:[{not:parseAnyDef(refs)},innerSchema]}:parseAnyDef(refs)};var parsePipelineDef=(def,refs)=>{if(refs.pipeStrategy==="input")return parseDef(def.in._def,refs);else if(refs.pipeStrategy==="output")return parseDef(def.out._def,refs);let a=parseDef(def.in._def,{...refs,currentPath:[...refs.currentPath,"allOf","0"]}),b=parseDef(def.out._def,{...refs,currentPath:[...refs.currentPath,"allOf",a?"1":"0"]});return{allOf:[a,b].filter((x)=>x!==void 0)}};function parsePromiseDef(def,refs){return parseDef(def.type._def,refs)}function parseSetDef(def,refs){let schema={type:"array",uniqueItems:!0,items:parseDef(def.valueType._def,{...refs,currentPath:[...refs.currentPath,"items"]})};if(def.minSize)setResponseValueAndErrors(schema,"minItems",def.minSize.value,def.minSize.message,refs);if(def.maxSize)setResponseValueAndErrors(schema,"maxItems",def.maxSize.value,def.maxSize.message,refs);return schema}function parseTupleDef(def,refs){if(def.rest)return{type:"array",minItems:def.items.length,items:def.items.map((x,i)=>parseDef(x._def,{...refs,currentPath:[...refs.currentPath,"items",`${i}`]})).reduce((acc,x)=>x===void 0?acc:[...acc,x],[]),additionalItems:parseDef(def.rest._def,{...refs,currentPath:[...refs.currentPath,"additionalItems"]})};else return{type:"array",minItems:def.items.length,maxItems:def.items.length,items:def.items.map((x,i)=>parseDef(x._def,{...refs,currentPath:[...refs.currentPath,"items",`${i}`]})).reduce((acc,x)=>x===void 0?acc:[...acc,x],[])}}function parseUndefinedDef(refs){return{not:parseAnyDef(refs)}}function parseUnknownDef(refs){return parseAnyDef(refs)}var parseReadonlyDef=(def,refs)=>parseDef(def.innerType._def,refs);var selectParser=(def,typeName,refs)=>{switch(typeName){case ZodFirstPartyTypeKind2.ZodString:return parseStringDef(def,refs);case ZodFirstPartyTypeKind2.ZodNumber:return parseNumberDef(def,refs);case ZodFirstPartyTypeKind2.ZodObject:return parseObjectDef(def,refs);case ZodFirstPartyTypeKind2.ZodBigInt:return parseBigintDef(def,refs);case ZodFirstPartyTypeKind2.ZodBoolean:return parseBooleanDef();case ZodFirstPartyTypeKind2.ZodDate:return parseDateDef(def,refs);case ZodFirstPartyTypeKind2.ZodUndefined:return parseUndefinedDef(refs);case ZodFirstPartyTypeKind2.ZodNull:return parseNullDef(refs);case ZodFirstPartyTypeKind2.ZodArray:return parseArrayDef(def,refs);case ZodFirstPartyTypeKind2.ZodUnion:case ZodFirstPartyTypeKind2.ZodDiscriminatedUnion:return parseUnionDef(def,refs);case ZodFirstPartyTypeKind2.ZodIntersection:return parseIntersectionDef(def,refs);case ZodFirstPartyTypeKind2.ZodTuple:return parseTupleDef(def,refs);case ZodFirstPartyTypeKind2.ZodRecord:return parseRecordDef(def,refs);case ZodFirstPartyTypeKind2.ZodLiteral:return parseLiteralDef(def,refs);case ZodFirstPartyTypeKind2.ZodEnum:return parseEnumDef(def);case ZodFirstPartyTypeKind2.ZodNativeEnum:return parseNativeEnumDef(def);case ZodFirstPartyTypeKind2.ZodNullable:return parseNullableDef(def,refs);case ZodFirstPartyTypeKind2.ZodOptional:return parseOptionalDef(def,refs);case ZodFirstPartyTypeKind2.ZodMap:return parseMapDef(def,refs);case ZodFirstPartyTypeKind2.ZodSet:return parseSetDef(def,refs);case ZodFirstPartyTypeKind2.ZodLazy:return()=>def.getter()._def;case ZodFirstPartyTypeKind2.ZodPromise:return parsePromiseDef(def,refs);case ZodFirstPartyTypeKind2.ZodNaN:case ZodFirstPartyTypeKind2.ZodNever:return parseNeverDef(refs);case ZodFirstPartyTypeKind2.ZodEffects:return parseEffectsDef(def,refs);case ZodFirstPartyTypeKind2.ZodAny:return parseAnyDef(refs);case ZodFirstPartyTypeKind2.ZodUnknown:return parseUnknownDef(refs);case ZodFirstPartyTypeKind2.ZodDefault:return parseDefaultDef(def,refs);case ZodFirstPartyTypeKind2.ZodBranded:return parseBrandedDef(def,refs);case ZodFirstPartyTypeKind2.ZodReadonly:return parseReadonlyDef(def,refs);case ZodFirstPartyTypeKind2.ZodCatch:return parseCatchDef(def,refs);case ZodFirstPartyTypeKind2.ZodPipeline:return parsePipelineDef(def,refs);case ZodFirstPartyTypeKind2.ZodFunction:case ZodFirstPartyTypeKind2.ZodVoid:case ZodFirstPartyTypeKind2.ZodSymbol:return;default:return((_)=>{return})(typeName)}};function parseDef(def,refs,forceResolution=!1){let seenItem=refs.seen.get(def);if(refs.override){let overrideResult=refs.override?.(def,refs,seenItem,forceResolution);if(overrideResult!==ignoreOverride)return overrideResult}if(seenItem&&!forceResolution){let seenSchema=get$ref(seenItem,refs);if(seenSchema!==void 0)return seenSchema}let newItem={def,path:refs.currentPath,jsonSchema:void 0};refs.seen.set(def,newItem);let jsonSchemaOrGetter=selectParser(def,def.typeName,refs),jsonSchema=typeof jsonSchemaOrGetter==="function"?parseDef(jsonSchemaOrGetter(),refs):jsonSchemaOrGetter;if(jsonSchema)addMeta(def,refs,jsonSchema);if(refs.postProcess){let postProcessResult=refs.postProcess(jsonSchema,def,refs);return newItem.jsonSchema=jsonSchema,postProcessResult}return newItem.jsonSchema=jsonSchema,jsonSchema}var get$ref=(item,refs)=>{switch(refs.$refStrategy){case"root":return{$ref:item.path.join("/")};case"relative":return{$ref:getRelativePath(refs.currentPath,item.path)};case"none":case"seen":{if(item.path.length<refs.currentPath.length&&item.path.every((value,index)=>refs.currentPath[index]===value))return console.warn(`Recursive reference detected at ${refs.currentPath.join("/")}! Defaulting to any`),parseAnyDef(refs);return refs.$refStrategy==="seen"?parseAnyDef(refs):void 0}}},addMeta=(def,refs,jsonSchema)=>{if(def.description){if(jsonSchema.description=def.description,refs.markdownDescription)jsonSchema.markdownDescription=def.description}return jsonSchema};var zodToJsonSchema=(schema,options)=>{let refs=getRefs(options),definitions=typeof options==="object"&&options.definitions?Object.entries(options.definitions).reduce((acc,[name,schema])=>({...acc,[name]:parseDef(schema._def,{...refs,currentPath:[...refs.basePath,refs.definitionPath,name]},!0)??parseAnyDef(refs)}),{}):void 0,name=typeof options==="string"?options:options?.nameStrategy==="title"?void 0:options?.name,main=parseDef(schema._def,name===void 0?refs:{...refs,currentPath:[...refs.basePath,refs.definitionPath,name]},!1)??parseAnyDef(refs),title=typeof options==="object"&&options.name!==void 0&&options.nameStrategy==="title"?options.name:void 0;if(title!==void 0)main.title=title;if(refs.flags.hasReferencedOpenAiAnyType){if(!definitions)definitions={};if(!definitions[refs.openAiAnyTypeName])definitions[refs.openAiAnyTypeName]={type:["string","number","integer","boolean","array","null"],items:{$ref:refs.$refStrategy==="relative"?"1":[...refs.basePath,refs.definitionPath,refs.openAiAnyTypeName].join("/")}}}let combined=name===void 0?definitions?{...main,[refs.definitionPath]:definitions}:main:{$ref:[...refs.$refStrategy==="relative"?[]:refs.basePath,refs.definitionPath,name].join("/"),[refs.definitionPath]:{...definitions,[name]:main}};if(refs.target==="jsonSchema7")combined.$schema="http://json-schema.org/draft-07/schema#";else if(refs.target==="jsonSchema2019-09"||refs.target==="openAi")combined.$schema="https://json-schema.org/draft/2019-09/schema#";if(refs.target==="openAi"&&(("anyOf"in combined)||("oneOf"in combined)||("allOf"in combined)||("type"in combined)&&Array.isArray(combined.type)))console.warn("Warning: OpenAI may not support schemas with unions as roots! Try wrapping it in an object property.");return combined};function mapMiniTarget(t){if(!t)return"draft-7";if(t==="jsonSchema7"||t==="draft-7")return"draft-7";if(t==="jsonSchema2019-09"||t==="draft-2020-12")return"draft-2020-12";return"draft-7"}function toJsonSchemaCompat(schema,opts){if(isZ4Schema(schema))return toJSONSchema(schema,{target:mapMiniTarget(opts?.target),io:opts?.pipeStrategy??"input"});return zodToJsonSchema(schema,{strictUnions:opts?.strictUnions??!0,pipeStrategy:opts?.pipeStrategy??"input"})}function getMethodLiteral(schema){let methodSchema=getObjectShape(schema)?.method;if(!methodSchema)throw Error("Schema is missing a method literal");let value=getLiteralValue(methodSchema);if(typeof value!=="string")throw Error("Schema method literal must be a string");return value}function parseWithCompat(schema,data){let result=safeParse2(schema,data);if(!result.success)throw result.error;return result.data}var DEFAULT_REQUEST_TIMEOUT_MSEC=60000;class Protocol{constructor(_options){if(this._options=_options,this._requestMessageId=0,this._requestHandlers=new Map,this._requestHandlerAbortControllers=new Map,this._notificationHandlers=new Map,this._responseHandlers=new Map,this._progressHandlers=new Map,this._timeoutInfo=new Map,this._pendingDebouncedNotifications=new Set,this._taskProgressTokens=new Map,this._requestResolvers=new Map,this.setNotificationHandler(CancelledNotificationSchema,(notification)=>{this._oncancel(notification)}),this.setNotificationHandler(ProgressNotificationSchema,(notification)=>{this._onprogress(notification)}),this.setRequestHandler(PingRequestSchema,(_request)=>({})),this._taskStore=_options?.taskStore,this._taskMessageQueue=_options?.taskMessageQueue,this._taskStore)this.setRequestHandler(GetTaskRequestSchema,async(request,extra)=>{let task=await this._taskStore.getTask(request.params.taskId,extra.sessionId);if(!task)throw new McpError(ErrorCode.InvalidParams,"Failed to retrieve task: Task not found");return{...task}}),this.setRequestHandler(GetTaskPayloadRequestSchema,async(request,extra)=>{let handleTaskResult=async()=>{let taskId=request.params.taskId;if(this._taskMessageQueue){let queuedMessage;while(queuedMessage=await this._taskMessageQueue.dequeue(taskId,extra.sessionId)){if(queuedMessage.type==="response"||queuedMessage.type==="error"){let message=queuedMessage.message,requestId=message.id,resolver=this._requestResolvers.get(requestId);if(resolver)if(this._requestResolvers.delete(requestId),queuedMessage.type==="response")resolver(message);else{let errorMessage=message,error=new McpError(errorMessage.error.code,errorMessage.error.message,errorMessage.error.data);resolver(error)}else{let messageType=queuedMessage.type==="response"?"Response":"Error";this._onerror(Error(`${messageType} handler missing for request ${requestId}`))}continue}await this._transport?.send(queuedMessage.message,{relatedRequestId:extra.requestId})}}let task=await this._taskStore.getTask(taskId,extra.sessionId);if(!task)throw new McpError(ErrorCode.InvalidParams,`Task not found: ${taskId}`);if(!isTerminal(task.status))return await this._waitForTaskUpdate(taskId,extra.signal),await handleTaskResult();if(isTerminal(task.status)){let result=await this._taskStore.getTaskResult(taskId,extra.sessionId);return this._clearTaskQueue(taskId),{...result,_meta:{...result._meta,[RELATED_TASK_META_KEY]:{taskId}}}}return await handleTaskResult()};return await handleTaskResult()}),this.setRequestHandler(ListTasksRequestSchema,async(request,extra)=>{try{let{tasks,nextCursor}=await this._taskStore.listTasks(request.params?.cursor,extra.sessionId);return{tasks,nextCursor,_meta:{}}}catch(error){throw new McpError(ErrorCode.InvalidParams,`Failed to list tasks: ${error instanceof Error?error.message:String(error)}`)}}),this.setRequestHandler(CancelTaskRequestSchema,async(request,extra)=>{try{let task=await this._taskStore.getTask(request.params.taskId,extra.sessionId);if(!task)throw new McpError(ErrorCode.InvalidParams,`Task not found: ${request.params.taskId}`);if(isTerminal(task.status))throw new McpError(ErrorCode.InvalidParams,`Cannot cancel task in terminal status: ${task.status}`);await this._taskStore.updateTaskStatus(request.params.taskId,"cancelled","Client cancelled task execution.",extra.sessionId),this._clearTaskQueue(request.params.taskId);let cancelledTask=await this._taskStore.getTask(request.params.taskId,extra.sessionId);if(!cancelledTask)throw new McpError(ErrorCode.InvalidParams,`Task not found after cancellation: ${request.params.taskId}`);return{_meta:{},...cancelledTask}}catch(error){if(error instanceof McpError)throw error;throw new McpError(ErrorCode.InvalidRequest,`Failed to cancel task: ${error instanceof Error?error.message:String(error)}`)}})}async _oncancel(notification){if(!notification.params.requestId)return;this._requestHandlerAbortControllers.get(notification.params.requestId)?.abort(notification.params.reason)}_setupTimeout(messageId,timeout,maxTotalTimeout,onTimeout,resetTimeoutOnProgress=!1){this._timeoutInfo.set(messageId,{timeoutId:setTimeout(onTimeout,timeout),startTime:Date.now(),timeout,maxTotalTimeout,resetTimeoutOnProgress,onTimeout})}_resetTimeout(messageId){let info=this._timeoutInfo.get(messageId);if(!info)return!1;let totalElapsed=Date.now()-info.startTime;if(info.maxTotalTimeout&&totalElapsed>=info.maxTotalTimeout)throw this._timeoutInfo.delete(messageId),McpError.fromError(ErrorCode.RequestTimeout,"Maximum total timeout exceeded",{maxTotalTimeout:info.maxTotalTimeout,totalElapsed});return clearTimeout(info.timeoutId),info.timeoutId=setTimeout(info.onTimeout,info.timeout),!0}_cleanupTimeout(messageId){let info=this._timeoutInfo.get(messageId);if(info)clearTimeout(info.timeoutId),this._timeoutInfo.delete(messageId)}async connect(transport){if(this._transport)throw Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");this._transport=transport;let _onclose=this.transport?.onclose;this._transport.onclose=()=>{_onclose?.(),this._onclose()};let _onerror=this.transport?.onerror;this._transport.onerror=(error)=>{_onerror?.(error),this._onerror(error)};let _onmessage=this._transport?.onmessage;this._transport.onmessage=(message,extra)=>{if(_onmessage?.(message,extra),isJSONRPCResultResponse(message)||isJSONRPCErrorResponse(message))this._onresponse(message);else if(isJSONRPCRequest(message))this._onrequest(message,extra);else if(isJSONRPCNotification(message))this._onnotification(message);else this._onerror(Error(`Unknown message type: ${JSON.stringify(message)}`))},await this._transport.start()}_onclose(){let responseHandlers=this._responseHandlers;this._responseHandlers=new Map,this._progressHandlers.clear(),this._taskProgressTokens.clear(),this._pendingDebouncedNotifications.clear();for(let info of this._timeoutInfo.values())clearTimeout(info.timeoutId);this._timeoutInfo.clear();for(let controller of this._requestHandlerAbortControllers.values())controller.abort();this._requestHandlerAbortControllers.clear();let error=McpError.fromError(ErrorCode.ConnectionClosed,"Connection closed");this._transport=void 0,this.onclose?.();for(let handler of responseHandlers.values())handler(error)}_onerror(error){this.onerror?.(error)}_onnotification(notification){let handler=this._notificationHandlers.get(notification.method)??this.fallbackNotificationHandler;if(handler===void 0)return;Promise.resolve().then(()=>handler(notification)).catch((error)=>this._onerror(Error(`Uncaught error in notification handler: ${error}`)))}_onrequest(request,extra){let handler=this._requestHandlers.get(request.method)??this.fallbackRequestHandler,capturedTransport=this._transport,relatedTaskId=request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;if(handler===void 0){let errorResponse={jsonrpc:"2.0",id:request.id,error:{code:ErrorCode.MethodNotFound,message:"Method not found"}};if(relatedTaskId&&this._taskMessageQueue)this._enqueueTaskMessage(relatedTaskId,{type:"error",message:errorResponse,timestamp:Date.now()},capturedTransport?.sessionId).catch((error)=>this._onerror(Error(`Failed to enqueue error response: ${error}`)));else capturedTransport?.send(errorResponse).catch((error)=>this._onerror(Error(`Failed to send an error response: ${error}`)));return}let abortController=new AbortController;this._requestHandlerAbortControllers.set(request.id,abortController);let taskCreationParams=isTaskAugmentedRequestParams(request.params)?request.params.task:void 0,taskStore=this._taskStore?this.requestTaskStore(request,capturedTransport?.sessionId):void 0,fullExtra={signal:abortController.signal,sessionId:capturedTransport?.sessionId,_meta:request.params?._meta,sendNotification:async(notification)=>{if(abortController.signal.aborted)return;let notificationOptions={relatedRequestId:request.id};if(relatedTaskId)notificationOptions.relatedTask={taskId:relatedTaskId};await this.notification(notification,notificationOptions)},sendRequest:async(r,resultSchema,options)=>{if(abortController.signal.aborted)throw new McpError(ErrorCode.ConnectionClosed,"Request was cancelled");let requestOptions={...options,relatedRequestId:request.id};if(relatedTaskId&&!requestOptions.relatedTask)requestOptions.relatedTask={taskId:relatedTaskId};let effectiveTaskId=requestOptions.relatedTask?.taskId??relatedTaskId;if(effectiveTaskId&&taskStore)await taskStore.updateTaskStatus(effectiveTaskId,"input_required");return await this.request(r,resultSchema,requestOptions)},authInfo:extra?.authInfo,requestId:request.id,requestInfo:extra?.requestInfo,taskId:relatedTaskId,taskStore,taskRequestedTtl:taskCreationParams?.ttl,closeSSEStream:extra?.closeSSEStream,closeStandaloneSSEStream:extra?.closeStandaloneSSEStream};Promise.resolve().then(()=>{if(taskCreationParams)this.assertTaskHandlerCapability(request.method)}).then(()=>handler(request,fullExtra)).then(async(result)=>{if(abortController.signal.aborted)return;let response={result,jsonrpc:"2.0",id:request.id};if(relatedTaskId&&this._taskMessageQueue)await this._enqueueTaskMessage(relatedTaskId,{type:"response",message:response,timestamp:Date.now()},capturedTransport?.sessionId);else await capturedTransport?.send(response)},async(error)=>{if(abortController.signal.aborted)return;let errorResponse={jsonrpc:"2.0",id:request.id,error:{code:Number.isSafeInteger(error.code)?error.code:ErrorCode.InternalError,message:error.message??"Internal error",...error.data!==void 0&&{data:error.data}}};if(relatedTaskId&&this._taskMessageQueue)await this._enqueueTaskMessage(relatedTaskId,{type:"error",message:errorResponse,timestamp:Date.now()},capturedTransport?.sessionId);else await capturedTransport?.send(errorResponse)}).catch((error)=>this._onerror(Error(`Failed to send response: ${error}`))).finally(()=>{if(this._requestHandlerAbortControllers.get(request.id)===abortController)this._requestHandlerAbortControllers.delete(request.id)})}_onprogress(notification){let{progressToken,...params}=notification.params,messageId=Number(progressToken),handler=this._progressHandlers.get(messageId);if(!handler){this._onerror(Error(`Received a progress notification for an unknown token: ${JSON.stringify(notification)}`));return}let responseHandler=this._responseHandlers.get(messageId),timeoutInfo=this._timeoutInfo.get(messageId);if(timeoutInfo&&responseHandler&&timeoutInfo.resetTimeoutOnProgress)try{this._resetTimeout(messageId)}catch(error){this._responseHandlers.delete(messageId),this._progressHandlers.delete(messageId),this._cleanupTimeout(messageId),responseHandler(error);return}handler(params)}_onresponse(response){let messageId=Number(response.id),resolver=this._requestResolvers.get(messageId);if(resolver){if(this._requestResolvers.delete(messageId),isJSONRPCResultResponse(response))resolver(response);else{let error=new McpError(response.error.code,response.error.message,response.error.data);resolver(error)}return}let handler=this._responseHandlers.get(messageId);if(handler===void 0){this._onerror(Error(`Received a response for an unknown message ID: ${JSON.stringify(response)}`));return}this._responseHandlers.delete(messageId),this._cleanupTimeout(messageId);let isTaskResponse=!1;if(isJSONRPCResultResponse(response)&&response.result&&typeof response.result==="object"){let result=response.result;if(result.task&&typeof result.task==="object"){let task=result.task;if(typeof task.taskId==="string")isTaskResponse=!0,this._taskProgressTokens.set(task.taskId,messageId)}}if(!isTaskResponse)this._progressHandlers.delete(messageId);if(isJSONRPCResultResponse(response))handler(response);else{let error=McpError.fromError(response.error.code,response.error.message,response.error.data);handler(error)}}get transport(){return this._transport}async close(){await this._transport?.close()}async*requestStream(request,resultSchema,options){let{task}=options??{};if(!task){try{yield{type:"result",result:await this.request(request,resultSchema,options)}}catch(error){yield{type:"error",error:error instanceof McpError?error:new McpError(ErrorCode.InternalError,String(error))}}return}let taskId;try{let createResult=await this.request(request,CreateTaskResultSchema,options);if(createResult.task)taskId=createResult.task.taskId,yield{type:"taskCreated",task:createResult.task};else throw new McpError(ErrorCode.InternalError,"Task creation did not return a task");while(!0){let task=await this.getTask({taskId},options);if(yield{type:"taskStatus",task},isTerminal(task.status)){if(task.status==="completed")yield{type:"result",result:await this.getTaskResult({taskId},resultSchema,options)};else if(task.status==="failed")yield{type:"error",error:new McpError(ErrorCode.InternalError,`Task ${taskId} failed`)};else if(task.status==="cancelled")yield{type:"error",error:new McpError(ErrorCode.InternalError,`Task ${taskId} was cancelled`)};return}if(task.status==="input_required"){yield{type:"result",result:await this.getTaskResult({taskId},resultSchema,options)};return}let pollInterval=task.pollInterval??this._options?.defaultTaskPollInterval??1000;await new Promise((resolve)=>setTimeout(resolve,pollInterval)),options?.signal?.throwIfAborted()}}catch(error){yield{type:"error",error:error instanceof McpError?error:new McpError(ErrorCode.InternalError,String(error))}}}request(request,resultSchema,options){let{relatedRequestId,resumptionToken,onresumptiontoken,task,relatedTask}=options??{};return new Promise((resolve,reject)=>{let earlyReject=(error)=>{reject(error)};if(!this._transport){earlyReject(Error("Not connected"));return}if(this._options?.enforceStrictCapabilities===!0)try{if(this.assertCapabilityForMethod(request.method),task)this.assertTaskCapability(request.method)}catch(e){earlyReject(e);return}options?.signal?.throwIfAborted();let messageId=this._requestMessageId++,jsonrpcRequest={...request,jsonrpc:"2.0",id:messageId};if(options?.onprogress)this._progressHandlers.set(messageId,options.onprogress),jsonrpcRequest.params={...request.params,_meta:{...request.params?._meta||{},progressToken:messageId}};if(task)jsonrpcRequest.params={...jsonrpcRequest.params,task};if(relatedTask)jsonrpcRequest.params={...jsonrpcRequest.params,_meta:{...jsonrpcRequest.params?._meta||{},[RELATED_TASK_META_KEY]:relatedTask}};let cancel=(reason)=>{this._responseHandlers.delete(messageId),this._progressHandlers.delete(messageId),this._cleanupTimeout(messageId),this._transport?.send({jsonrpc:"2.0",method:"notifications/cancelled",params:{requestId:messageId,reason:String(reason)}},{relatedRequestId,resumptionToken,onresumptiontoken}).catch((error)=>this._onerror(Error(`Failed to send cancellation: ${error}`)));let error=reason instanceof McpError?reason:new McpError(ErrorCode.RequestTimeout,String(reason));reject(error)};this._responseHandlers.set(messageId,(response)=>{if(options?.signal?.aborted)return;if(response instanceof Error)return reject(response);try{let parseResult=safeParse2(resultSchema,response.result);if(!parseResult.success)reject(parseResult.error);else resolve(parseResult.data)}catch(error){reject(error)}}),options?.signal?.addEventListener("abort",()=>{cancel(options?.signal?.reason)});let timeout=options?.timeout??DEFAULT_REQUEST_TIMEOUT_MSEC,timeoutHandler=()=>cancel(McpError.fromError(ErrorCode.RequestTimeout,"Request timed out",{timeout}));this._setupTimeout(messageId,timeout,options?.maxTotalTimeout,timeoutHandler,options?.resetTimeoutOnProgress??!1);let relatedTaskId=relatedTask?.taskId;if(relatedTaskId){let responseResolver=(response)=>{let handler=this._responseHandlers.get(messageId);if(handler)handler(response);else this._onerror(Error(`Response handler missing for side-channeled request ${messageId}`))};this._requestResolvers.set(messageId,responseResolver),this._enqueueTaskMessage(relatedTaskId,{type:"request",message:jsonrpcRequest,timestamp:Date.now()}).catch((error)=>{this._cleanupTimeout(messageId),reject(error)})}else this._transport.send(jsonrpcRequest,{relatedRequestId,resumptionToken,onresumptiontoken}).catch((error)=>{this._cleanupTimeout(messageId),reject(error)})})}async getTask(params,options){return this.request({method:"tasks/get",params},GetTaskResultSchema,options)}async getTaskResult(params,resultSchema,options){return this.request({method:"tasks/result",params},resultSchema,options)}async listTasks(params,options){return this.request({method:"tasks/list",params},ListTasksResultSchema,options)}async cancelTask(params,options){return this.request({method:"tasks/cancel",params},CancelTaskResultSchema,options)}async notification(notification,options){if(!this._transport)throw Error("Not connected");this.assertNotificationCapability(notification.method);let relatedTaskId=options?.relatedTask?.taskId;if(relatedTaskId){let jsonrpcNotification={...notification,jsonrpc:"2.0",params:{...notification.params,_meta:{...notification.params?._meta||{},[RELATED_TASK_META_KEY]:options.relatedTask}}};await this._enqueueTaskMessage(relatedTaskId,{type:"notification",message:jsonrpcNotification,timestamp:Date.now()});return}if((this._options?.debouncedNotificationMethods??[]).includes(notification.method)&&!notification.params&&!options?.relatedRequestId&&!options?.relatedTask){if(this._pendingDebouncedNotifications.has(notification.method))return;this._pendingDebouncedNotifications.add(notification.method),Promise.resolve().then(()=>{if(this._pendingDebouncedNotifications.delete(notification.method),!this._transport)return;let jsonrpcNotification={...notification,jsonrpc:"2.0"};if(options?.relatedTask)jsonrpcNotification={...jsonrpcNotification,params:{...jsonrpcNotification.params,_meta:{...jsonrpcNotification.params?._meta||{},[RELATED_TASK_META_KEY]:options.relatedTask}}};this._transport?.send(jsonrpcNotification,options).catch((error)=>this._onerror(error))});return}let jsonrpcNotification={...notification,jsonrpc:"2.0"};if(options?.relatedTask)jsonrpcNotification={...jsonrpcNotification,params:{...jsonrpcNotification.params,_meta:{...jsonrpcNotification.params?._meta||{},[RELATED_TASK_META_KEY]:options.relatedTask}}};await this._transport.send(jsonrpcNotification,options)}setRequestHandler(requestSchema,handler){let method=getMethodLiteral(requestSchema);this.assertRequestHandlerCapability(method),this._requestHandlers.set(method,(request,extra)=>{let parsed=parseWithCompat(requestSchema,request);return Promise.resolve(handler(parsed,extra))})}removeRequestHandler(method){this._requestHandlers.delete(method)}assertCanSetRequestHandler(method){if(this._requestHandlers.has(method))throw Error(`A request handler for ${method} already exists, which would be overridden`)}setNotificationHandler(notificationSchema,handler){let method=getMethodLiteral(notificationSchema);this._notificationHandlers.set(method,(notification)=>{let parsed=parseWithCompat(notificationSchema,notification);return Promise.resolve(handler(parsed))})}removeNotificationHandler(method){this._notificationHandlers.delete(method)}_cleanupTaskProgressHandler(taskId){let progressToken=this._taskProgressTokens.get(taskId);if(progressToken!==void 0)this._progressHandlers.delete(progressToken),this._taskProgressTokens.delete(taskId)}async _enqueueTaskMessage(taskId,message,sessionId){if(!this._taskStore||!this._taskMessageQueue)throw Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");let maxQueueSize=this._options?.maxTaskQueueSize;await this._taskMessageQueue.enqueue(taskId,message,sessionId,maxQueueSize)}async _clearTaskQueue(taskId,sessionId){if(this._taskMessageQueue){let messages=await this._taskMessageQueue.dequeueAll(taskId,sessionId);for(let message of messages)if(message.type==="request"&&isJSONRPCRequest(message.message)){let requestId=message.message.id,resolver=this._requestResolvers.get(requestId);if(resolver)resolver(new McpError(ErrorCode.InternalError,"Task cancelled or completed")),this._requestResolvers.delete(requestId);else this._onerror(Error(`Resolver missing for request ${requestId} during task ${taskId} cleanup`))}}}async _waitForTaskUpdate(taskId,signal){let interval=this._options?.defaultTaskPollInterval??1000;try{let task=await this._taskStore?.getTask(taskId);if(task?.pollInterval)interval=task.pollInterval}catch{}return new Promise((resolve,reject)=>{if(signal.aborted){reject(new McpError(ErrorCode.InvalidRequest,"Request cancelled"));return}let timeoutId=setTimeout(resolve,interval);signal.addEventListener("abort",()=>{clearTimeout(timeoutId),reject(new McpError(ErrorCode.InvalidRequest,"Request cancelled"))},{once:!0})})}requestTaskStore(request,sessionId){let taskStore=this._taskStore;if(!taskStore)throw Error("No task store configured");return{createTask:async(taskParams)=>{if(!request)throw Error("No request provided");return await taskStore.createTask(taskParams,request.id,{method:request.method,params:request.params},sessionId)},getTask:async(taskId)=>{let task=await taskStore.getTask(taskId,sessionId);if(!task)throw new McpError(ErrorCode.InvalidParams,"Failed to retrieve task: Task not found");return task},storeTaskResult:async(taskId,status,result)=>{await taskStore.storeTaskResult(taskId,status,result,sessionId);let task=await taskStore.getTask(taskId,sessionId);if(task){let notification=TaskStatusNotificationSchema.parse({method:"notifications/tasks/status",params:task});if(await this.notification(notification),isTerminal(task.status))this._cleanupTaskProgressHandler(taskId)}},getTaskResult:(taskId)=>taskStore.getTaskResult(taskId,sessionId),updateTaskStatus:async(taskId,status,statusMessage)=>{let task=await taskStore.getTask(taskId,sessionId);if(!task)throw new McpError(ErrorCode.InvalidParams,`Task "${taskId}" not found - it may have been cleaned up`);if(isTerminal(task.status))throw new McpError(ErrorCode.InvalidParams,`Cannot update task "${taskId}" from terminal status "${task.status}" to "${status}". Terminal states (completed, failed, cancelled) cannot transition to other states.`);await taskStore.updateTaskStatus(taskId,status,statusMessage,sessionId);let updatedTask=await taskStore.getTask(taskId,sessionId);if(updatedTask){let notification=TaskStatusNotificationSchema.parse({method:"notifications/tasks/status",params:updatedTask});if(await this.notification(notification),isTerminal(updatedTask.status))this._cleanupTaskProgressHandler(taskId)}},listTasks:(cursor)=>taskStore.listTasks(cursor,sessionId)}}}function isPlainObject(value){return value!==null&&typeof value==="object"&&!Array.isArray(value)}function mergeCapabilities(base,additional){let result={...base};for(let key in additional){let k=key,addValue=additional[k];if(addValue===void 0)continue;let baseValue=result[k];if(isPlainObject(baseValue)&&isPlainObject(addValue))result[k]={...baseValue,...addValue};else result[k]=addValue}return result}var import_ajv=__toESM(require_ajv(),1),import_ajv_formats=__toESM(require_dist(),1);function createDefaultAjvInstance(){let ajv=new import_ajv.default({strict:!1,validateFormats:!0,validateSchema:!1,allErrors:!0});return import_ajv_formats.default(ajv),ajv}class AjvJsonSchemaValidator{constructor(ajv){this._ajv=ajv??createDefaultAjvInstance()}getValidator(schema){let ajvValidator="$id"in schema&&typeof schema.$id==="string"?this._ajv.getSchema(schema.$id)??this._ajv.compile(schema):this._ajv.compile(schema);return(input)=>{if(ajvValidator(input))return{valid:!0,data:input,errorMessage:void 0};else return{valid:!1,data:void 0,errorMessage:this._ajv.errorsText(ajvValidator.errors)}}}}class ExperimentalServerTasks{constructor(_server){this._server=_server}requestStream(request,resultSchema,options){return this._server.requestStream(request,resultSchema,options)}createMessageStream(params,options){let clientCapabilities=this._server.getClientCapabilities();if((params.tools||params.toolChoice)&&!clientCapabilities?.sampling?.tools)throw Error("Client does not support sampling tools capability.");if(params.messages.length>0){let lastMessage=params.messages[params.messages.length-1],lastContent=Array.isArray(lastMessage.content)?lastMessage.content:[lastMessage.content],hasToolResults=lastContent.some((c)=>c.type==="tool_result"),previousMessage=params.messages.length>1?params.messages[params.messages.length-2]:void 0,previousContent=previousMessage?Array.isArray(previousMessage.content)?previousMessage.content:[previousMessage.content]:[],hasPreviousToolUse=previousContent.some((c)=>c.type==="tool_use");if(hasToolResults){if(lastContent.some((c)=>c.type!=="tool_result"))throw Error("The last message must contain only tool_result content if any is present");if(!hasPreviousToolUse)throw Error("tool_result blocks are not matching any tool_use from the previous message")}if(hasPreviousToolUse){let toolUseIds=new Set(previousContent.filter((c)=>c.type==="tool_use").map((c)=>c.id)),toolResultIds=new Set(lastContent.filter((c)=>c.type==="tool_result").map((c)=>c.toolUseId));if(toolUseIds.size!==toolResultIds.size||![...toolUseIds].every((id)=>toolResultIds.has(id)))throw Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}return this.requestStream({method:"sampling/createMessage",params},CreateMessageResultSchema,options)}elicitInputStream(params,options){let clientCapabilities=this._server.getClientCapabilities(),mode=params.mode??"form";switch(mode){case"url":{if(!clientCapabilities?.elicitation?.url)throw Error("Client does not support url elicitation.");break}case"form":{if(!clientCapabilities?.elicitation?.form)throw Error("Client does not support form elicitation.");break}}let normalizedParams=mode==="form"&¶ms.mode===void 0?{...params,mode:"form"}:params;return this.requestStream({method:"elicitation/create",params:normalizedParams},ElicitResultSchema,options)}async getTask(taskId,options){return this._server.getTask({taskId},options)}async getTaskResult(taskId,resultSchema,options){return this._server.getTaskResult({taskId},resultSchema,options)}async listTasks(cursor,options){return this._server.listTasks(cursor?{cursor}:void 0,options)}async cancelTask(taskId,options){return this._server.cancelTask({taskId},options)}}function assertToolsCallTaskCapability(requests,method,entityName){if(!requests)throw Error(`${entityName} does not support task creation (required for ${method})`);switch(method){case"tools/call":if(!requests.tools?.call)throw Error(`${entityName} does not support task creation for tools/call (required for ${method})`);break;default:break}}function assertClientRequestTaskCapability(requests,method,entityName){if(!requests)throw Error(`${entityName} does not support task creation (required for ${method})`);switch(method){case"sampling/createMessage":if(!requests.sampling?.createMessage)throw Error(`${entityName} does not support task creation for sampling/createMessage (required for ${method})`);break;case"elicitation/create":if(!requests.elicitation?.create)throw Error(`${entityName} does not support task creation for elicitation/create (required for ${method})`);break;default:break}}class Server extends Protocol{constructor(_serverInfo,options){super(options);if(this._serverInfo=_serverInfo,this._loggingLevels=new Map,this.LOG_LEVEL_SEVERITY=new Map(LoggingLevelSchema.options.map((level,index)=>[level,index])),this.isMessageIgnored=(level,sessionId)=>{let currentLevel=this._loggingLevels.get(sessionId);return currentLevel?this.LOG_LEVEL_SEVERITY.get(level)<this.LOG_LEVEL_SEVERITY.get(currentLevel):!1},this._capabilities=options?.capabilities??{},this._instructions=options?.instructions,this._jsonSchemaValidator=options?.jsonSchemaValidator??new AjvJsonSchemaValidator,this.setRequestHandler(InitializeRequestSchema,(request)=>this._oninitialize(request)),this.setNotificationHandler(InitializedNotificationSchema,()=>this.oninitialized?.()),this._capabilities.logging)this.setRequestHandler(SetLevelRequestSchema,async(request,extra)=>{let transportSessionId=extra.sessionId||extra.requestInfo?.headers["mcp-session-id"]||void 0,{level}=request.params,parseResult=LoggingLevelSchema.safeParse(level);if(parseResult.success)this._loggingLevels.set(transportSessionId,parseResult.data);return{}})}get experimental(){if(!this._experimental)this._experimental={tasks:new ExperimentalServerTasks(this)};return this._experimental}registerCapabilities(capabilities){if(this.transport)throw Error("Cannot register capabilities after connecting to transport");this._capabilities=mergeCapabilities(this._capabilities,capabilities)}setRequestHandler(requestSchema,handler){let methodSchema=getObjectShape(requestSchema)?.method;if(!methodSchema)throw Error("Schema is missing a method literal");let methodValue=getLiteralValue(methodSchema);if(typeof methodValue!=="string")throw Error("Schema method literal must be a string");if(methodValue==="tools/call"){let wrappedHandler=async(request,extra)=>{let validatedRequest=safeParse2(CallToolRequestSchema,request);if(!validatedRequest.success){let errorMessage=validatedRequest.error instanceof Error?validatedRequest.error.message:String(validatedRequest.error);throw new McpError(ErrorCode.InvalidParams,`Invalid tools/call request: ${errorMessage}`)}let{params}=validatedRequest.data,result=await Promise.resolve(handler(request,extra));if(params.task){let taskValidationResult=safeParse2(CreateTaskResultSchema,result);if(!taskValidationResult.success){let errorMessage=taskValidationResult.error instanceof Error?taskValidationResult.error.message:String(taskValidationResult.error);throw new McpError(ErrorCode.InvalidParams,`Invalid task creation result: ${errorMessage}`)}return taskValidationResult.data}let validationResult=safeParse2(CallToolResultSchema,result);if(!validationResult.success){let errorMessage=validationResult.error instanceof Error?validationResult.error.message:String(validationResult.error);throw new McpError(ErrorCode.InvalidParams,`Invalid tools/call result: ${errorMessage}`)}return validationResult.data};return super.setRequestHandler(requestSchema,wrappedHandler)}return super.setRequestHandler(requestSchema,handler)}assertCapabilityForMethod(method){switch(method){case"sampling/createMessage":if(!this._clientCapabilities?.sampling)throw Error(`Client does not support sampling (required for ${method})`);break;case"elicitation/create":if(!this._clientCapabilities?.elicitation)throw Error(`Client does not support elicitation (required for ${method})`);break;case"roots/list":if(!this._clientCapabilities?.roots)throw Error(`Client does not support listing roots (required for ${method})`);break;case"ping":break}}assertNotificationCapability(method){switch(method){case"notifications/message":if(!this._capabilities.logging)throw Error(`Server does not support logging (required for ${method})`);break;case"notifications/resources/updated":case"notifications/resources/list_changed":if(!this._capabilities.resources)throw Error(`Server does not support notifying about resources (required for ${method})`);break;case"notifications/tools/list_changed":if(!this._capabilities.tools)throw Error(`Server does not support notifying of tool list changes (required for ${method})`);break;case"notifications/prompts/list_changed":if(!this._capabilities.prompts)throw Error(`Server does not support notifying of prompt list changes (required for ${method})`);break;case"notifications/elicitation/complete":if(!this._clientCapabilities?.elicitation?.url)throw Error(`Client does not support URL elicitation (required for ${method})`);break;case"notifications/cancelled":break;case"notifications/progress":break}}assertRequestHandlerCapability(method){if(!this._capabilities)return;switch(method){case"completion/complete":if(!this._capabilities.completions)throw Error(`Server does not support completions (required for ${method})`);break;case"logging/setLevel":if(!this._capabilities.logging)throw Error(`Server does not support logging (required for ${method})`);break;case"prompts/get":case"prompts/list":if(!this._capabilities.prompts)throw Error(`Server does not support prompts (required for ${method})`);break;case"resources/list":case"resources/templates/list":case"resources/read":if(!this._capabilities.resources)throw Error(`Server does not support resources (required for ${method})`);break;case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Server does not support tools (required for ${method})`);break;case"tasks/get":case"tasks/list":case"tasks/result":case"tasks/cancel":if(!this._capabilities.tasks)throw Error(`Server does not support tasks capability (required for ${method})`);break;case"ping":case"initialize":break}}assertTaskCapability(method){assertClientRequestTaskCapability(this._clientCapabilities?.tasks?.requests,method,"Client")}assertTaskHandlerCapability(method){if(!this._capabilities)return;assertToolsCallTaskCapability(this._capabilities.tasks?.requests,method,"Server")}async _oninitialize(request){let requestedVersion=request.params.protocolVersion;return this._clientCapabilities=request.params.capabilities,this._clientVersion=request.params.clientInfo,{protocolVersion:SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)?requestedVersion:LATEST_PROTOCOL_VERSION,capabilities:this.getCapabilities(),serverInfo:this._serverInfo,...this._instructions&&{instructions:this._instructions}}}getClientCapabilities(){return this._clientCapabilities}getClientVersion(){return this._clientVersion}getCapabilities(){return this._capabilities}async ping(){return this.request({method:"ping"},EmptyResultSchema)}async createMessage(params,options){if(params.tools||params.toolChoice){if(!this._clientCapabilities?.sampling?.tools)throw Error("Client does not support sampling tools capability.")}if(params.messages.length>0){let lastMessage=params.messages[params.messages.length-1],lastContent=Array.isArray(lastMessage.content)?lastMessage.content:[lastMessage.content],hasToolResults=lastContent.some((c)=>c.type==="tool_result"),previousMessage=params.messages.length>1?params.messages[params.messages.length-2]:void 0,previousContent=previousMessage?Array.isArray(previousMessage.content)?previousMessage.content:[previousMessage.content]:[],hasPreviousToolUse=previousContent.some((c)=>c.type==="tool_use");if(hasToolResults){if(lastContent.some((c)=>c.type!=="tool_result"))throw Error("The last message must contain only tool_result content if any is present");if(!hasPreviousToolUse)throw Error("tool_result blocks are not matching any tool_use from the previous message")}if(hasPreviousToolUse){let toolUseIds=new Set(previousContent.filter((c)=>c.type==="tool_use").map((c)=>c.id)),toolResultIds=new Set(lastContent.filter((c)=>c.type==="tool_result").map((c)=>c.toolUseId));if(toolUseIds.size!==toolResultIds.size||![...toolUseIds].every((id)=>toolResultIds.has(id)))throw Error("ids of tool_result blocks and tool_use blocks from previous message do not match")}}if(params.tools)return this.request({method:"sampling/createMessage",params},CreateMessageResultWithToolsSchema,options);return this.request({method:"sampling/createMessage",params},CreateMessageResultSchema,options)}async elicitInput(params,options){switch(params.mode??"form"){case"url":{if(!this._clientCapabilities?.elicitation?.url)throw Error("Client does not support url elicitation.");let urlParams=params;return this.request({method:"elicitation/create",params:urlParams},ElicitResultSchema,options)}case"form":{if(!this._clientCapabilities?.elicitation?.form)throw Error("Client does not support form elicitation.");let formParams=params.mode==="form"?params:{...params,mode:"form"},result=await this.request({method:"elicitation/create",params:formParams},ElicitResultSchema,options);if(result.action==="accept"&&result.content&&formParams.requestedSchema)try{let validationResult=this._jsonSchemaValidator.getValidator(formParams.requestedSchema)(result.content);if(!validationResult.valid)throw new McpError(ErrorCode.InvalidParams,`Elicitation response content does not match requested schema: ${validationResult.errorMessage}`)}catch(error){if(error instanceof McpError)throw error;throw new McpError(ErrorCode.InternalError,`Error validating elicitation response: ${error instanceof Error?error.message:String(error)}`)}return result}}}createElicitationCompletionNotifier(elicitationId,options){if(!this._clientCapabilities?.elicitation?.url)throw Error("Client does not support URL elicitation (required for notifications/elicitation/complete)");return()=>this.notification({method:"notifications/elicitation/complete",params:{elicitationId}},options)}async listRoots(params,options){return this.request({method:"roots/list",params},ListRootsResultSchema,options)}async sendLoggingMessage(params,sessionId){if(this._capabilities.logging){if(!this.isMessageIgnored(params.level,sessionId))return this.notification({method:"notifications/message",params})}}async sendResourceUpdated(params){return this.notification({method:"notifications/resources/updated",params})}async sendResourceListChanged(){return this.notification({method:"notifications/resources/list_changed"})}async sendToolListChanged(){return this.notification({method:"notifications/tools/list_changed"})}async sendPromptListChanged(){return this.notification({method:"notifications/prompts/list_changed"})}}var COMPLETABLE_SYMBOL=Symbol.for("mcp.completable");function isCompletable(schema){return!!schema&&typeof schema==="object"&&COMPLETABLE_SYMBOL in schema}function getCompleter(schema){return schema[COMPLETABLE_SYMBOL]?.complete}var McpZodTypeKind;(function(McpZodTypeKind){McpZodTypeKind.Completable="McpCompletable"})(McpZodTypeKind||(McpZodTypeKind={}));var TOOL_NAME_REGEX=/^[A-Za-z0-9._-]{1,128}$/;function validateToolName(name){let warnings=[];if(name.length===0)return{isValid:!1,warnings:["Tool name cannot be empty"]};if(name.length>128)return{isValid:!1,warnings:[`Tool name exceeds maximum length of 128 characters (current: ${name.length})`]};if(name.includes(" "))warnings.push("Tool name contains spaces, which may cause parsing issues");if(name.includes(","))warnings.push("Tool name contains commas, which may cause parsing issues");if(name.startsWith("-")||name.endsWith("-"))warnings.push("Tool name starts or ends with a dash, which may cause parsing issues in some contexts");if(name.startsWith(".")||name.endsWith("."))warnings.push("Tool name starts or ends with a dot, which may cause parsing issues in some contexts");if(!TOOL_NAME_REGEX.test(name)){let invalidChars=name.split("").filter((char)=>!/[A-Za-z0-9._-]/.test(char)).filter((char,index,arr)=>arr.indexOf(char)===index);return warnings.push(`Tool name contains invalid characters: ${invalidChars.map((c)=>`"${c}"`).join(", ")}`,"Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)"),{isValid:!1,warnings}}return{isValid:!0,warnings}}function issueToolNameWarning(name,warnings){if(warnings.length>0){console.warn(`Tool name validation warning for "${name}":`);for(let warning of warnings)console.warn(` - ${warning}`);console.warn("Tool registration will proceed, but this may cause compatibility issues."),console.warn("Consider updating the tool name to conform to the MCP tool naming standard."),console.warn("See SEP: Specify Format for Tool Names (https://github.com/modelcontextprotocol/modelcontextprotocol/issues/986) for more details.")}}function validateAndWarnToolName(name){let result=validateToolName(name);return issueToolNameWarning(name,result.warnings),result.isValid}class ExperimentalMcpServerTasks{constructor(_mcpServer){this._mcpServer=_mcpServer}registerToolTask(name,config,handler){let execution={taskSupport:"required",...config.execution};if(execution.taskSupport==="forbidden")throw Error(`Cannot register task-based tool '${name}' with taskSupport 'forbidden'. Use registerTool() instead.`);return this._mcpServer._createRegisteredTool(name,config.title,config.description,config.inputSchema,config.outputSchema,config.annotations,execution,config._meta,handler)}}class McpServer{constructor(serverInfo,options){this._registeredResources={},this._registeredResourceTemplates={},this._registeredTools={},this._registeredPrompts={},this._toolHandlersInitialized=!1,this._completionHandlerInitialized=!1,this._resourceHandlersInitialized=!1,this._promptHandlersInitialized=!1,this.server=new Server(serverInfo,options)}get experimental(){if(!this._experimental)this._experimental={tasks:new ExperimentalMcpServerTasks(this)};return this._experimental}async connect(transport){return await this.server.connect(transport)}async close(){await this.server.close()}setToolRequestHandlers(){if(this._toolHandlersInitialized)return;this.server.assertCanSetRequestHandler(getMethodValue(ListToolsRequestSchema)),this.server.assertCanSetRequestHandler(getMethodValue(CallToolRequestSchema)),this.server.registerCapabilities({tools:{listChanged:!0}}),this.server.setRequestHandler(ListToolsRequestSchema,()=>({tools:Object.entries(this._registeredTools).filter(([,tool])=>tool.enabled).map(([name,tool])=>{let toolDefinition={name,title:tool.title,description:tool.description,inputSchema:(()=>{let obj=normalizeObjectSchema(tool.inputSchema);return obj?toJsonSchemaCompat(obj,{strictUnions:!0,pipeStrategy:"input"}):EMPTY_OBJECT_JSON_SCHEMA})(),annotations:tool.annotations,execution:tool.execution,_meta:tool._meta};if(tool.outputSchema){let obj=normalizeObjectSchema(tool.outputSchema);if(obj)toolDefinition.outputSchema=toJsonSchemaCompat(obj,{strictUnions:!0,pipeStrategy:"output"})}return toolDefinition})})),this.server.setRequestHandler(CallToolRequestSchema,async(request,extra)=>{try{let tool=this._registeredTools[request.params.name];if(!tool)throw new McpError(ErrorCode.InvalidParams,`Tool ${request.params.name} not found`);if(!tool.enabled)throw new McpError(ErrorCode.InvalidParams,`Tool ${request.params.name} disabled`);let isTaskRequest=!!request.params.task,taskSupport=tool.execution?.taskSupport,isTaskHandler="createTask"in tool.handler;if((taskSupport==="required"||taskSupport==="optional")&&!isTaskHandler)throw new McpError(ErrorCode.InternalError,`Tool ${request.params.name} has taskSupport '${taskSupport}' but was not registered with registerToolTask`);if(taskSupport==="required"&&!isTaskRequest)throw new McpError(ErrorCode.MethodNotFound,`Tool ${request.params.name} requires task augmentation (taskSupport: 'required')`);if(taskSupport==="optional"&&!isTaskRequest&&isTaskHandler)return await this.handleAutomaticTaskPolling(tool,request,extra);let args=await this.validateToolInput(tool,request.params.arguments,request.params.name),result=await this.executeToolHandler(tool,args,extra);if(isTaskRequest)return result;return await this.validateToolOutput(tool,result,request.params.name),result}catch(error){if(error instanceof McpError){if(error.code===ErrorCode.UrlElicitationRequired)throw error}return this.createToolError(error instanceof Error?error.message:String(error))}}),this._toolHandlersInitialized=!0}createToolError(errorMessage){return{content:[{type:"text",text:errorMessage}],isError:!0}}async validateToolInput(tool,args,toolName){if(!tool.inputSchema)return;let schemaToParse=normalizeObjectSchema(tool.inputSchema)??tool.inputSchema,parseResult=await safeParseAsync2(schemaToParse,args);if(!parseResult.success){let error="error"in parseResult?parseResult.error:"Unknown error",errorMessage=getParseErrorMessage(error);throw new McpError(ErrorCode.InvalidParams,`Input validation error: Invalid arguments for tool ${toolName}: ${errorMessage}`)}return parseResult.data}async validateToolOutput(tool,result,toolName){if(!tool.outputSchema)return;if(!("content"in result))return;if(result.isError)return;if(!result.structuredContent)throw new McpError(ErrorCode.InvalidParams,`Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);let outputObj=normalizeObjectSchema(tool.outputSchema),parseResult=await safeParseAsync2(outputObj,result.structuredContent);if(!parseResult.success){let error="error"in parseResult?parseResult.error:"Unknown error",errorMessage=getParseErrorMessage(error);throw new McpError(ErrorCode.InvalidParams,`Output validation error: Invalid structured content for tool ${toolName}: ${errorMessage}`)}}async executeToolHandler(tool,args,extra){let handler=tool.handler;if("createTask"in handler){if(!extra.taskStore)throw Error("No task store provided.");let taskExtra={...extra,taskStore:extra.taskStore};if(tool.inputSchema)return await Promise.resolve(handler.createTask(args,taskExtra));else return await Promise.resolve(handler.createTask(taskExtra))}if(tool.inputSchema)return await Promise.resolve(handler(args,extra));else return await Promise.resolve(handler(extra))}async handleAutomaticTaskPolling(tool,request,extra){if(!extra.taskStore)throw Error("No task store provided for task-capable tool.");let args=await this.validateToolInput(tool,request.params.arguments,request.params.name),handler=tool.handler,taskExtra={...extra,taskStore:extra.taskStore},createTaskResult=args?await Promise.resolve(handler.createTask(args,taskExtra)):await Promise.resolve(handler.createTask(taskExtra)),taskId=createTaskResult.task.taskId,task=createTaskResult.task,pollInterval=task.pollInterval??5000;while(task.status!=="completed"&&task.status!=="failed"&&task.status!=="cancelled"){await new Promise((resolve)=>setTimeout(resolve,pollInterval));let updatedTask=await extra.taskStore.getTask(taskId);if(!updatedTask)throw new McpError(ErrorCode.InternalError,`Task ${taskId} not found during polling`);task=updatedTask}return await extra.taskStore.getTaskResult(taskId)}setCompletionRequestHandler(){if(this._completionHandlerInitialized)return;this.server.assertCanSetRequestHandler(getMethodValue(CompleteRequestSchema)),this.server.registerCapabilities({completions:{}}),this.server.setRequestHandler(CompleteRequestSchema,async(request)=>{switch(request.params.ref.type){case"ref/prompt":return assertCompleteRequestPrompt(request),this.handlePromptCompletion(request,request.params.ref);case"ref/resource":return assertCompleteRequestResourceTemplate(request),this.handleResourceCompletion(request,request.params.ref);default:throw new McpError(ErrorCode.InvalidParams,`Invalid completion reference: ${request.params.ref}`)}}),this._completionHandlerInitialized=!0}async handlePromptCompletion(request,ref){let prompt=this._registeredPrompts[ref.name];if(!prompt)throw new McpError(ErrorCode.InvalidParams,`Prompt ${ref.name} not found`);if(!prompt.enabled)throw new McpError(ErrorCode.InvalidParams,`Prompt ${ref.name} disabled`);if(!prompt.argsSchema)return EMPTY_COMPLETION_RESULT;let field=getObjectShape(prompt.argsSchema)?.[request.params.argument.name];if(!isCompletable(field))return EMPTY_COMPLETION_RESULT;let completer=getCompleter(field);if(!completer)return EMPTY_COMPLETION_RESULT;let suggestions=await completer(request.params.argument.value,request.params.context);return createCompletionResult(suggestions)}async handleResourceCompletion(request,ref){let template=Object.values(this._registeredResourceTemplates).find((t)=>t.resourceTemplate.uriTemplate.toString()===ref.uri);if(!template){if(this._registeredResources[ref.uri])return EMPTY_COMPLETION_RESULT;throw new McpError(ErrorCode.InvalidParams,`Resource template ${request.params.ref.uri} not found`)}let completer=template.resourceTemplate.completeCallback(request.params.argument.name);if(!completer)return EMPTY_COMPLETION_RESULT;let suggestions=await completer(request.params.argument.value,request.params.context);return createCompletionResult(suggestions)}setResourceRequestHandlers(){if(this._resourceHandlersInitialized)return;this.server.assertCanSetRequestHandler(getMethodValue(ListResourcesRequestSchema)),this.server.assertCanSetRequestHandler(getMethodValue(ListResourceTemplatesRequestSchema)),this.server.assertCanSetRequestHandler(getMethodValue(ReadResourceRequestSchema)),this.server.registerCapabilities({resources:{listChanged:!0}}),this.server.setRequestHandler(ListResourcesRequestSchema,async(request,extra)=>{let resources=Object.entries(this._registeredResources).filter(([_,resource])=>resource.enabled).map(([uri,resource])=>({uri,name:resource.name,...resource.metadata})),templateResources=[];for(let template of Object.values(this._registeredResourceTemplates)){if(!template.resourceTemplate.listCallback)continue;let result=await template.resourceTemplate.listCallback(extra);for(let resource of result.resources)templateResources.push({...template.metadata,...resource})}return{resources:[...resources,...templateResources]}}),this.server.setRequestHandler(ListResourceTemplatesRequestSchema,async()=>({resourceTemplates:Object.entries(this._registeredResourceTemplates).map(([name,template])=>({name,uriTemplate:template.resourceTemplate.uriTemplate.toString(),...template.metadata}))})),this.server.setRequestHandler(ReadResourceRequestSchema,async(request,extra)=>{let uri=new URL(request.params.uri),resource=this._registeredResources[uri.toString()];if(resource){if(!resource.enabled)throw new McpError(ErrorCode.InvalidParams,`Resource ${uri} disabled`);return resource.readCallback(uri,extra)}for(let template of Object.values(this._registeredResourceTemplates)){let variables=template.resourceTemplate.uriTemplate.match(uri.toString());if(variables)return template.readCallback(uri,variables,extra)}throw new McpError(ErrorCode.InvalidParams,`Resource ${uri} not found`)}),this._resourceHandlersInitialized=!0}setPromptRequestHandlers(){if(this._promptHandlersInitialized)return;this.server.assertCanSetRequestHandler(getMethodValue(ListPromptsRequestSchema)),this.server.assertCanSetRequestHandler(getMethodValue(GetPromptRequestSchema)),this.server.registerCapabilities({prompts:{listChanged:!0}}),this.server.setRequestHandler(ListPromptsRequestSchema,()=>({prompts:Object.entries(this._registeredPrompts).filter(([,prompt])=>prompt.enabled).map(([name,prompt])=>({name,title:prompt.title,description:prompt.description,arguments:prompt.argsSchema?promptArgumentsFromSchema(prompt.argsSchema):void 0}))})),this.server.setRequestHandler(GetPromptRequestSchema,async(request,extra)=>{let prompt=this._registeredPrompts[request.params.name];if(!prompt)throw new McpError(ErrorCode.InvalidParams,`Prompt ${request.params.name} not found`);if(!prompt.enabled)throw new McpError(ErrorCode.InvalidParams,`Prompt ${request.params.name} disabled`);if(prompt.argsSchema){let argsObj=normalizeObjectSchema(prompt.argsSchema),parseResult=await safeParseAsync2(argsObj,request.params.arguments);if(!parseResult.success){let error="error"in parseResult?parseResult.error:"Unknown error",errorMessage=getParseErrorMessage(error);throw new McpError(ErrorCode.InvalidParams,`Invalid arguments for prompt ${request.params.name}: ${errorMessage}`)}let args=parseResult.data,cb=prompt.callback;return await Promise.resolve(cb(args,extra))}else{let cb=prompt.callback;return await Promise.resolve(cb(extra))}}),this._promptHandlersInitialized=!0}resource(name,uriOrTemplate,...rest){let metadata;if(typeof rest[0]==="object")metadata=rest.shift();let readCallback=rest[0];if(typeof uriOrTemplate==="string"){if(this._registeredResources[uriOrTemplate])throw Error(`Resource ${uriOrTemplate} is already registered`);let registeredResource=this._createRegisteredResource(name,void 0,uriOrTemplate,metadata,readCallback);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),registeredResource}else{if(this._registeredResourceTemplates[name])throw Error(`Resource template ${name} is already registered`);let registeredResourceTemplate=this._createRegisteredResourceTemplate(name,void 0,uriOrTemplate,metadata,readCallback);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),registeredResourceTemplate}}registerResource(name,uriOrTemplate,config,readCallback){if(typeof uriOrTemplate==="string"){if(this._registeredResources[uriOrTemplate])throw Error(`Resource ${uriOrTemplate} is already registered`);let registeredResource=this._createRegisteredResource(name,config.title,uriOrTemplate,config,readCallback);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),registeredResource}else{if(this._registeredResourceTemplates[name])throw Error(`Resource template ${name} is already registered`);let registeredResourceTemplate=this._createRegisteredResourceTemplate(name,config.title,uriOrTemplate,config,readCallback);return this.setResourceRequestHandlers(),this.sendResourceListChanged(),registeredResourceTemplate}}_createRegisteredResource(name,title,uri,metadata,readCallback){let registeredResource={name,title,metadata,readCallback,enabled:!0,disable:()=>registeredResource.update({enabled:!1}),enable:()=>registeredResource.update({enabled:!0}),remove:()=>registeredResource.update({uri:null}),update:(updates)=>{if(typeof updates.uri<"u"&&updates.uri!==uri){if(delete this._registeredResources[uri],updates.uri)this._registeredResources[updates.uri]=registeredResource}if(typeof updates.name<"u")registeredResource.name=updates.name;if(typeof updates.title<"u")registeredResource.title=updates.title;if(typeof updates.metadata<"u")registeredResource.metadata=updates.metadata;if(typeof updates.callback<"u")registeredResource.readCallback=updates.callback;if(typeof updates.enabled<"u")registeredResource.enabled=updates.enabled;this.sendResourceListChanged()}};return this._registeredResources[uri]=registeredResource,registeredResource}_createRegisteredResourceTemplate(name,title,template,metadata,readCallback){let registeredResourceTemplate={resourceTemplate:template,title,metadata,readCallback,enabled:!0,disable:()=>registeredResourceTemplate.update({enabled:!1}),enable:()=>registeredResourceTemplate.update({enabled:!0}),remove:()=>registeredResourceTemplate.update({name:null}),update:(updates)=>{if(typeof updates.name<"u"&&updates.name!==name){if(delete this._registeredResourceTemplates[name],updates.name)this._registeredResourceTemplates[updates.name]=registeredResourceTemplate}if(typeof updates.title<"u")registeredResourceTemplate.title=updates.title;if(typeof updates.template<"u")registeredResourceTemplate.resourceTemplate=updates.template;if(typeof updates.metadata<"u")registeredResourceTemplate.metadata=updates.metadata;if(typeof updates.callback<"u")registeredResourceTemplate.readCallback=updates.callback;if(typeof updates.enabled<"u")registeredResourceTemplate.enabled=updates.enabled;this.sendResourceListChanged()}};this._registeredResourceTemplates[name]=registeredResourceTemplate;let variableNames=template.uriTemplate.variableNames;if(Array.isArray(variableNames)&&variableNames.some((v)=>!!template.completeCallback(v)))this.setCompletionRequestHandler();return registeredResourceTemplate}_createRegisteredPrompt(name,title,description,argsSchema,callback){let registeredPrompt={title,description,argsSchema:argsSchema===void 0?void 0:objectFromShape(argsSchema),callback,enabled:!0,disable:()=>registeredPrompt.update({enabled:!1}),enable:()=>registeredPrompt.update({enabled:!0}),remove:()=>registeredPrompt.update({name:null}),update:(updates)=>{if(typeof updates.name<"u"&&updates.name!==name){if(delete this._registeredPrompts[name],updates.name)this._registeredPrompts[updates.name]=registeredPrompt}if(typeof updates.title<"u")registeredPrompt.title=updates.title;if(typeof updates.description<"u")registeredPrompt.description=updates.description;if(typeof updates.argsSchema<"u")registeredPrompt.argsSchema=objectFromShape(updates.argsSchema);if(typeof updates.callback<"u")registeredPrompt.callback=updates.callback;if(typeof updates.enabled<"u")registeredPrompt.enabled=updates.enabled;this.sendPromptListChanged()}};if(this._registeredPrompts[name]=registeredPrompt,argsSchema){if(Object.values(argsSchema).some((field)=>{let inner=field instanceof ZodOptional?field._def?.innerType:field;return isCompletable(inner)}))this.setCompletionRequestHandler()}return registeredPrompt}_createRegisteredTool(name,title,description,inputSchema,outputSchema,annotations,execution,_meta,handler){validateAndWarnToolName(name);let registeredTool={title,description,inputSchema:getZodSchemaObject(inputSchema),outputSchema:getZodSchemaObject(outputSchema),annotations,execution,_meta,handler,enabled:!0,disable:()=>registeredTool.update({enabled:!1}),enable:()=>registeredTool.update({enabled:!0}),remove:()=>registeredTool.update({name:null}),update:(updates)=>{if(typeof updates.name<"u"&&updates.name!==name){if(typeof updates.name==="string")validateAndWarnToolName(updates.name);if(delete this._registeredTools[name],updates.name)this._registeredTools[updates.name]=registeredTool}if(typeof updates.title<"u")registeredTool.title=updates.title;if(typeof updates.description<"u")registeredTool.description=updates.description;if(typeof updates.paramsSchema<"u")registeredTool.inputSchema=objectFromShape(updates.paramsSchema);if(typeof updates.outputSchema<"u")registeredTool.outputSchema=objectFromShape(updates.outputSchema);if(typeof updates.callback<"u")registeredTool.handler=updates.callback;if(typeof updates.annotations<"u")registeredTool.annotations=updates.annotations;if(typeof updates._meta<"u")registeredTool._meta=updates._meta;if(typeof updates.enabled<"u")registeredTool.enabled=updates.enabled;this.sendToolListChanged()}};return this._registeredTools[name]=registeredTool,this.setToolRequestHandlers(),this.sendToolListChanged(),registeredTool}tool(name,...rest){if(this._registeredTools[name])throw Error(`Tool ${name} is already registered`);let description,inputSchema,outputSchema,annotations;if(typeof rest[0]==="string")description=rest.shift();if(rest.length>1){let firstArg=rest[0];if(isZodRawShapeCompat(firstArg)){if(inputSchema=rest.shift(),rest.length>1&&typeof rest[0]==="object"&&rest[0]!==null&&!isZodRawShapeCompat(rest[0]))annotations=rest.shift()}else if(typeof firstArg==="object"&&firstArg!==null){if(Object.values(firstArg).some((v)=>typeof v==="object"&&v!==null))throw Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);annotations=rest.shift()}}let callback=rest[0];return this._createRegisteredTool(name,void 0,description,inputSchema,outputSchema,annotations,{taskSupport:"forbidden"},void 0,callback)}registerTool(name,config,cb){if(this._registeredTools[name])throw Error(`Tool ${name} is already registered`);let{title,description,inputSchema,outputSchema,annotations,_meta}=config;return this._createRegisteredTool(name,title,description,inputSchema,outputSchema,annotations,{taskSupport:"forbidden"},_meta,cb)}prompt(name,...rest){if(this._registeredPrompts[name])throw Error(`Prompt ${name} is already registered`);let description;if(typeof rest[0]==="string")description=rest.shift();let argsSchema;if(rest.length>1)argsSchema=rest.shift();let cb=rest[0],registeredPrompt=this._createRegisteredPrompt(name,void 0,description,argsSchema,cb);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),registeredPrompt}registerPrompt(name,config,cb){if(this._registeredPrompts[name])throw Error(`Prompt ${name} is already registered`);let{title,description,argsSchema}=config,registeredPrompt=this._createRegisteredPrompt(name,title,description,argsSchema,cb);return this.setPromptRequestHandlers(),this.sendPromptListChanged(),registeredPrompt}isConnected(){return this.server.transport!==void 0}async sendLoggingMessage(params,sessionId){return this.server.sendLoggingMessage(params,sessionId)}sendResourceListChanged(){if(this.isConnected())this.server.sendResourceListChanged()}sendToolListChanged(){if(this.isConnected())this.server.sendToolListChanged()}sendPromptListChanged(){if(this.isConnected())this.server.sendPromptListChanged()}}var EMPTY_OBJECT_JSON_SCHEMA={type:"object",properties:{}};function isZodTypeLike(value){return value!==null&&typeof value==="object"&&"parse"in value&&typeof value.parse==="function"&&"safeParse"in value&&typeof value.safeParse==="function"}function isZodSchemaInstance(obj){return"_def"in obj||"_zod"in obj||isZodTypeLike(obj)}function isZodRawShapeCompat(obj){if(typeof obj!=="object"||obj===null)return!1;if(isZodSchemaInstance(obj))return!1;if(Object.keys(obj).length===0)return!0;return Object.values(obj).some(isZodTypeLike)}function getZodSchemaObject(schema){if(!schema)return;if(isZodRawShapeCompat(schema))return objectFromShape(schema);if(!isZodSchemaInstance(schema))throw Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");return schema}function promptArgumentsFromSchema(schema){let shape=getObjectShape(schema);if(!shape)return[];return Object.entries(shape).map(([name,field])=>{let description=getSchemaDescription(field),isOptional=isSchemaOptional(field);return{name,description,required:!isOptional}})}function getMethodValue(schema){let methodSchema=getObjectShape(schema)?.method;if(!methodSchema)throw Error("Schema is missing a method literal");let value=getLiteralValue(methodSchema);if(typeof value==="string")return value;throw Error("Schema method literal must be a string")}function createCompletionResult(suggestions){return{completion:{values:suggestions.slice(0,100),total:suggestions.length,hasMore:suggestions.length>100}}}var EMPTY_COMPLETION_RESULT={completion:{values:[],hasMore:!1}};import process2 from"process";var STDIO_DEFAULT_MAX_BUFFER_SIZE=10485760;class ReadBuffer{constructor(options){this._maxBufferSize=options?.maxBufferSize??STDIO_DEFAULT_MAX_BUFFER_SIZE}append(chunk){if((this._buffer?.length??0)+chunk.length>this._maxBufferSize)throw this.clear(),Error(`ReadBuffer exceeded maximum size of ${this._maxBufferSize} bytes`);this._buffer=this._buffer?Buffer.concat([this._buffer,chunk]):chunk}readMessage(){if(!this._buffer)return null;let index=this._buffer.indexOf(`
|
|
132
132
|
`);if(index===-1)return null;let line=this._buffer.toString("utf8",0,index).replace(/\r$/,"");return this._buffer=this._buffer.subarray(index+1),deserializeMessage(line)}clear(){this._buffer=void 0}}function deserializeMessage(line){return JSONRPCMessageSchema.parse(JSON.parse(line))}function serializeMessage(message){return JSON.stringify(message)+`
|
|
133
|
-
`}class StdioServerTransport{constructor(_stdin=process2.stdin,_stdout=process2.stdout,options){this._stdin=_stdin,this._stdout=_stdout,this._started=!1,this._ondata=(chunk)=>{try{this._readBuffer.append(chunk),this.processReadBuffer()}catch(error){this.onerror?.(error),this.close().catch(()=>{})}},this._onerror=(error)=>{this.onerror?.(error)},this._readBuffer=new ReadBuffer({maxBufferSize:options?.maxBufferSize})}async start(){if(this._started)throw Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){while(!0)try{let message=this._readBuffer.readMessage();if(message===null)break;this.onmessage?.(message)}catch(error){this.onerror?.(error)}}async close(){if(this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0)this._stdin.pause();this._readBuffer.clear(),this.onclose?.()}send(message){return new Promise((resolve)=>{let json=serializeMessage(message);if(this._stdout.write(json))resolve();else this._stdout.once("drain",resolve)})}}var SERVER_VERSION="0.
|
|
133
|
+
`}class StdioServerTransport{constructor(_stdin=process2.stdin,_stdout=process2.stdout,options){this._stdin=_stdin,this._stdout=_stdout,this._started=!1,this._ondata=(chunk)=>{try{this._readBuffer.append(chunk),this.processReadBuffer()}catch(error){this.onerror?.(error),this.close().catch(()=>{})}},this._onerror=(error)=>{this.onerror?.(error)},this._readBuffer=new ReadBuffer({maxBufferSize:options?.maxBufferSize})}async start(){if(this._started)throw Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){while(!0)try{let message=this._readBuffer.readMessage();if(message===null)break;this.onmessage?.(message)}catch(error){this.onerror?.(error)}}async close(){if(this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0)this._stdin.pause();this._readBuffer.clear(),this.onclose?.()}send(message){return new Promise((resolve)=>{let json=serializeMessage(message);if(this._stdout.write(json))resolve();else this._stdout.once("drain",resolve)})}}var SERVER_VERSION="0.2.0",MCP_SERVER_INFO={name:"velloo",version:SERVER_VERSION};function gateInstructions(opts){let mismatch=`Velloo could not open the design folder at ${opts.root}: it is on design-folder format v${opts.found}, but this velloo reads v${opts.current}. The canvas and all `+"design tools are unavailable until that is resolved \u2014 this session only carries "+`this notice${opts.upgrade?" and the migration tool":""}.`,fix=opts.upgrade?"To fix it, call the `upgrade_design_folder` tool \u2014 it migrates the folder in "+`place, exactly like running \`velloo upgrade ${opts.root}\` in a shell (which also works, if you prefer). After it succeeds, reconnect this MCP server (restart it in your client) to load the design tools.`:"The folder was written by a NEWER velloo than this one \u2014 nothing here can migrate "+"it backwards. The velloo CLI itself must be updated (e.g. its global install), then reconnect this MCP server.";return`${mismatch}
|
|
134
134
|
|
|
135
135
|
${fix}
|
|
136
136
|
|
|
137
|
-
Tell the user what happened and how it was (or must be) resolved.`}async function runStdioFormatGate(opts){let mcp=new McpServer(MCP_SERVER_INFO,{instructions:gateInstructions(opts)}),upgrade=opts.upgrade;if(upgrade)mcp.registerTool("upgrade_design_folder",{description:`Migrate the design folder at ${opts.root} from format v${opts.found} to v${opts.current} in place \u2014 the same migration as \`velloo upgrade <
|
|
137
|
+
Tell the user what happened and how it was (or must be) resolved.`}async function runStdioFormatGate(opts){let mcp=new McpServer(MCP_SERVER_INFO,{instructions:gateInstructions(opts)}),upgrade=opts.upgrade;if(upgrade)mcp.registerTool("upgrade_design_folder",{description:`Migrate the design folder at ${opts.root} from format v${opts.found} to v${opts.current} in place \u2014 the same migration as \`velloo upgrade <design>\`. `+"After it succeeds, reconnect this MCP server to load the design tools."},async()=>{try{let result=await upgrade();return{content:[{type:"text",text:`${result.applied.length===0?`${opts.root} is already at format v${result.to} \u2014 nothing to migrate.`:[`Migrated ${opts.root} from format v${result.from} to v${result.to}:`,...result.applied.map((s)=>`- ${s}`),`Files rewritten: ${result.changedFiles.join(", ")}`].join(`
|
|
138
138
|
`)}
|
|
139
139
|
|
|
140
140
|
The design tools are still not loaded in this session \u2014 `+"reconnect the velloo MCP server now (restart it in your client). If you cannot trigger a reconnect yourself, ask the user to do it."}]}}catch(err){return{content:[{type:"text",text:`Upgrade failed: ${err instanceof Error?err.message:String(err)}`}],isError:!0}}});let closing=!1,close=async()=>{if(closing)return;closing=!0,await mcp.close().catch(()=>{return}),opts.onExit?.()};return mcp.server.onclose=()=>void close(),await mcp.connect(new StdioServerTransport),{close}}/*!
|
|
@@ -150,13 +150,13 @@ ${value}`,dataLines++}else isEventPrefix(chunk,searchIndex,firstCharCode)?eventT
|
|
|
150
150
|
`,searchIndex)}return chunk.slice(searchIndex)}for(;searchIndex<chunk.length;){let crIndex=chunk.indexOf("\r",searchIndex),lfIndex=chunk.indexOf(`
|
|
151
151
|
`,searchIndex),lineEnd=-1;if(crIndex!==-1&&lfIndex!==-1?lineEnd=crIndex<lfIndex?crIndex:lfIndex:crIndex!==-1?crIndex===chunk.length-1?lineEnd=-1:lineEnd=crIndex:lfIndex!==-1&&(lineEnd=lfIndex),lineEnd===-1)break;parseLine(chunk,searchIndex,lineEnd),searchIndex=lineEnd+1,chunk.charCodeAt(searchIndex-1)===CR&&chunk.charCodeAt(searchIndex)===LF&&searchIndex++}return chunk.slice(searchIndex)}function parseLine(chunk,start,end){if(start===end){dispatchEvent();return}let firstCharCode=chunk.charCodeAt(start);if(isDataPrefix(chunk,start,firstCharCode)){let valueStart=chunk.charCodeAt(start+5)===SPACE?start+6:start+5,value2=chunk.slice(valueStart,end);data=dataLines===0?value2:`${data}
|
|
152
152
|
${value2}`,dataLines++;return}if(isEventPrefix(chunk,start,firstCharCode)){eventType=chunk.slice(chunk.charCodeAt(start+6)===SPACE?start+7:start+6,end)||void 0;return}if(firstCharCode===105&&chunk.charCodeAt(start+1)===100&&chunk.charCodeAt(start+2)===58){let value2=chunk.slice(chunk.charCodeAt(start+3)===SPACE?start+4:start+3,end);value2.includes("\x00")||(id=value2);return}if(firstCharCode===58){if(onComment){let line2=chunk.slice(start,end);onComment(line2.slice(chunk.charCodeAt(start+1)===SPACE?2:1))}return}let line=chunk.slice(start,end),fieldSeparatorIndex=line.indexOf(":");if(fieldSeparatorIndex===-1){processField(line,"",line);return}let field=line.slice(0,fieldSeparatorIndex),offset=line.charCodeAt(fieldSeparatorIndex+1)===SPACE?2:1,value=line.slice(fieldSeparatorIndex+offset);processField(field,value,line)}function processField(field,value,line){switch(field){case"event":eventType=value||void 0;break;case"data":data=dataLines===0?value:`${data}
|
|
153
|
-
${value}`,dataLines++;break;case"id":value.includes("\x00")||(id=value);break;case"retry":/^\d+$/.test(value)?onRetry(parseInt(value,10)):onError(new ParseError(`Invalid \`retry\` value: "${value}"`,{type:"invalid-retry",value,line}));break;default:onError(new ParseError(`Unknown field "${field.length>20?`${field.slice(0,20)}\u2026`:field}"`,{type:"unknown-field",field,value,line}));break}}function dispatchEvent(){dataLines>0&&onEvent({id,event:eventType,data}),id=void 0,data="",dataLines=0,eventType=void 0}function reset(options={}){if(options.consume&&pendingFragments.length>0){let incompleteLine=pendingFragments.join("");parseLine(incompleteLine,0,incompleteLine.length)}isFirstChunk=!0,id=void 0,data="",dataLines=0,eventType=void 0,pendingFragments.length=0,pendingFragmentsLength=0,terminated=!1}return{feed,reset}}function isDataPrefix(chunk,i,firstCharCode){return firstCharCode===100&&chunk.charCodeAt(i+1)===97&&chunk.charCodeAt(i+2)===116&&chunk.charCodeAt(i+3)===97&&chunk.charCodeAt(i+4)===58}function isEventPrefix(chunk,i,firstCharCode){return firstCharCode===101&&chunk.charCodeAt(i+1)===118&&chunk.charCodeAt(i+2)===101&&chunk.charCodeAt(i+3)===110&&chunk.charCodeAt(i+4)===116&&chunk.charCodeAt(i+5)===58}class EventSourceParserStream extends TransformStream{constructor({onError,onRetry,onComment,maxBufferSize}={}){let parser;super({start(controller){parser=createParser({onEvent:(event)=>{controller.enqueue(event)},onError(error){typeof onError=="function"&&onError(error),(onError==="terminate"||error.type==="max-buffer-size-exceeded")&&controller.error(error)},onRetry,onComment,maxBufferSize})},transform(chunk){parser.feed(chunk)}})}}var DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class StreamableHTTPError extends Error{constructor(code,message){super(`Streamable HTTP error: ${message}`);this.code=code}}class StreamableHTTPClientTransport{constructor(url,opts){this._hasCompletedAuthFlow=!1,this._url=url,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=opts?.requestInit,this._authProvider=opts?.authProvider,this._fetch=opts?.fetch,this._fetchWithInit=createFetchWithInit(opts?.fetch,opts?.requestInit),this._sessionId=opts?.sessionId,this._reconnectionOptions=opts?.reconnectionOptions??DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS}async _authThenStart(){if(!this._authProvider)throw new UnauthorizedError("No auth provider");let result;try{result=await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(error){throw this.onerror?.(error),error}if(result!=="AUTHORIZED")throw new UnauthorizedError;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let headers={};if(this._authProvider){let tokens=await this._authProvider.tokens();if(tokens)headers.Authorization=`Bearer ${tokens.access_token}`}if(this._sessionId)headers["mcp-session-id"]=this._sessionId;if(this._protocolVersion)headers["mcp-protocol-version"]=this._protocolVersion;let extraHeaders=normalizeHeaders(this._requestInit?.headers);return new Headers({...headers,...extraHeaders})}async _startOrAuthSse(options){let{resumptionToken}=options;try{let headers=await this._commonHeaders();if(headers.set("Accept","text/event-stream"),resumptionToken)headers.set("last-event-id",resumptionToken);let response=await(this._fetch??fetch)(this._url,{method:"GET",headers,signal:this._abortController?.signal});if(!response.ok){if(await response.body?.cancel(),response.status===401&&this._authProvider)return await this._authThenStart();if(response.status===405)return;throw new StreamableHTTPError(response.status,`Failed to open SSE stream: ${response.statusText}`)}this._handleSseStream(response.body,options,!0)}catch(error){throw this.onerror?.(error),error}}_getNextReconnectionDelay(attempt){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let initialDelay=this._reconnectionOptions.initialReconnectionDelay,growFactor=this._reconnectionOptions.reconnectionDelayGrowFactor,maxDelay=this._reconnectionOptions.maxReconnectionDelay;return Math.min(initialDelay*Math.pow(growFactor,attempt),maxDelay)}_scheduleReconnection(options,attemptCount=0){let maxRetries=this._reconnectionOptions.maxRetries;if(attemptCount>=maxRetries){this.onerror?.(Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));return}let delay=this._getNextReconnectionDelay(attemptCount);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(options).catch((error)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${error instanceof Error?error.message:String(error)}`)),this._scheduleReconnection(options,attemptCount+1)})},delay)}_handleSseStream(stream,options,isReconnectable){if(!stream)return;let{onresumptiontoken,replayMessageId}=options,lastEventId,hasPrimingEvent=!1,receivedResponse=!1;(async()=>{try{let reader=stream.pipeThrough(new TextDecoderStream).pipeThrough(new EventSourceParserStream({onRetry:(retryMs)=>{this._serverRetryMs=retryMs}})).getReader();while(!0){let{value:event,done}=await reader.read();if(done)break;if(event.id)lastEventId=event.id,hasPrimingEvent=!0,onresumptiontoken?.(event.id);if(!event.data)continue;if(!event.event||event.event==="message")try{let message=JSONRPCMessageSchema.parse(JSON.parse(event.data));if(isJSONRPCResultResponse(message)){if(receivedResponse=!0,replayMessageId!==void 0)message.id=replayMessageId}this.onmessage?.(message)}catch(error){this.onerror?.(error)}}if((isReconnectable||hasPrimingEvent)&&!receivedResponse&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:lastEventId,onresumptiontoken,replayMessageId},0)}catch(error){if(this.onerror?.(Error(`SSE stream disconnected: ${error}`)),(isReconnectable||hasPrimingEvent)&&!receivedResponse&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:lastEventId,onresumptiontoken,replayMessageId},0)}catch(error){this.onerror?.(Error(`Failed to reconnect: ${error instanceof Error?error.message:String(error)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(authorizationCode){if(!this._authProvider)throw new UnauthorizedError("No auth provider");if(await auth(this._authProvider,{serverUrl:this._url,authorizationCode,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new UnauthorizedError("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(message,options){try{let{resumptionToken,onresumptiontoken}=options||{};if(resumptionToken){this._startOrAuthSse({resumptionToken,replayMessageId:isJSONRPCRequest(message)?message.id:void 0}).catch((err)=>this.onerror?.(err));return}let headers=await this._commonHeaders();headers.set("content-type","application/json"),headers.set("accept","application/json, text/event-stream");let init={...this._requestInit,method:"POST",headers,body:JSON.stringify(message),signal:this._abortController?.signal},response=await(this._fetch??fetch)(this._url,init),sessionId=response.headers.get("mcp-session-id");if(sessionId)this._sessionId=sessionId;if(!response.ok){let text=await response.text().catch(()=>null);if(response.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new StreamableHTTPError(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl,scope}=extractWWWAuthenticateParams(response);if(this._resourceMetadataUrl=resourceMetadataUrl,this._scope=scope,await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new UnauthorizedError;return this._hasCompletedAuthFlow=!0,this.send(message)}if(response.status===403&&this._authProvider){let{resourceMetadataUrl,scope,error}=extractWWWAuthenticateParams(response);if(error==="insufficient_scope"){let wwwAuthHeader=response.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===wwwAuthHeader)throw new StreamableHTTPError(403,"Server returned 403 after trying upscoping");if(scope)this._scope=scope;if(resourceMetadataUrl)this._resourceMetadataUrl=resourceMetadataUrl;if(this._lastUpscopingHeader=wwwAuthHeader??void 0,await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new UnauthorizedError;return this.send(message)}}throw new StreamableHTTPError(response.status,`Error POSTing to endpoint: ${text}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,response.status===202){if(await response.body?.cancel(),isInitializedNotification(message))this._startOrAuthSse({resumptionToken:void 0}).catch((err)=>this.onerror?.(err));return}let hasRequests=(Array.isArray(message)?message:[message]).filter((msg)=>("method"in msg)&&("id"in msg)&&msg.id!==void 0).length>0,contentType=response.headers.get("content-type"),responseMediaType=mediaTypeEssence(contentType);if(hasRequests)if(responseMediaType==="text/event-stream")this._handleSseStream(response.body,{onresumptiontoken},!1);else if(responseMediaType==="application/json"){let data=await response.json(),responseMessages=Array.isArray(data)?data.map((msg)=>JSONRPCMessageSchema.parse(msg)):[JSONRPCMessageSchema.parse(data)];for(let msg of responseMessages)this.onmessage?.(msg)}else throw await response.body?.cancel(),new StreamableHTTPError(-1,`Unexpected content type: ${contentType}`);else await response.body?.cancel()}catch(error){throw this.onerror?.(error),error}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let headers=await this._commonHeaders(),init={...this._requestInit,method:"DELETE",headers,signal:this._abortController?.signal},response=await(this._fetch??fetch)(this._url,init);if(await response.body?.cancel(),!response.ok&&response.status!==405)throw new StreamableHTTPError(response.status,`Failed to terminate session: ${response.statusText}`);this._sessionId=void 0}catch(error){throw this.onerror?.(error),error}}setProtocolVersion(version){this._protocolVersion=version}get protocolVersion(){return this._protocolVersion}async resumeStream(lastEventId,options){await this._startOrAuthSse({resumptionToken:lastEventId,onresumptiontoken:options?.onresumptiontoken})}}async function runStdioMcpProxy(httpMcpUrl,opts={}){let stdio=opts.agentTransport??new StdioServerTransport,closing=!1,initRequest=null,reinitIds=new Set,reinitSeq=0,reconnecting=null,wire=(transport)=>(transport.onmessage=(msg)=>{if(isJSONRPCResponse(msg)&&typeof msg.id==="string"&&reinitIds.has(msg.id)){reinitIds.delete(msg.id);return}stdio.send(msg).catch(()=>{return})},transport.onclose=()=>{if(transport===http)close()},transport.onerror=(err)=>console.error("velloo mcp: canvas daemon connection error:",err),transport),http=wire(new StreamableHTTPClientTransport(new URL(httpMcpUrl))),close=async()=>{if(closing)return;closing=!0,await stdio.close().catch(()=>{return}),await http.close().catch(()=>{return}),opts.onExit?.()},reconnect=async(replayInit)=>{if(!opts.rediscover)return!1;let url=await opts.rediscover().catch(()=>null);if(!url||closing)return!1;let next=wire(new StreamableHTTPClientTransport(new URL(url)));try{if(await next.start(),replayInit&&initRequest){let reinitId=`velloo-reinit-${++reinitSeq}`;reinitIds.add(reinitId),await next.send({...initRequest,id:reinitId}),await next.send({jsonrpc:"2.0",method:"notifications/initialized"})}}catch(err){return console.error("velloo mcp: reconnect to respawned canvas daemon failed:",err),await next.close().catch(()=>{return}),!1}let prev=http;return http=next,await prev.close().catch(()=>{return}),console.error(`velloo mcp: reconnected to the canvas daemon at ${url}`),!0},reconnectOnce=(replayInit)=>(reconnecting??=reconnect(replayInit).finally(()=>{reconnecting=null}),reconnecting),forward=async(msg)=>{let error;try{await http.send(msg);return}catch(err){error=err}console.error("velloo mcp: forwarding to canvas daemon failed:",error);let isInit=isJSONRPCRequest(msg)&&msg.method==="initialize";if(await reconnectOnce(!isInit))try{await http.send(msg);return}catch(err){error=err}if(isJSONRPCRequest(msg)){let detail=error instanceof Error?error.message:String(error),reply={jsonrpc:"2.0",id:msg.id,error:{code:-32000,message:opts.rediscover?`velloo: the canvas daemon is unreachable and could not be respawned (${detail}) \u2014 check .design/cache/daemon.log, then retry`:`velloo: forwarding to the canvas daemon failed (${detail}) \u2014 retry`}};stdio.send(reply).catch(()=>{return})}};return stdio.onmessage=(msg)=>{if(isJSONRPCRequest(msg)&&msg.method==="initialize")initRequest=msg;forward(msg)},stdio.onclose=()=>void close(),await http.start(),await stdio.start(),{close}}import{existsSync as existsSync9}from"fs";import{extname as extname3,join as join37,sep as sep3}from"path";var compose=(middleware,onError,onNotFound)=>(context,next)=>{let index=-1;return dispatch(0);async function dispatch(i){if(i<=index)throw Error("next() called multiple times");index=i;let res,isError=!1,handler;if(middleware[i])handler=middleware[i][0][0],context.req.routeIndex=i;else handler=i===middleware.length&&next||void 0;if(handler)try{res=await handler(context,()=>dispatch(i+1))}catch(err){if(err instanceof Error&&onError)context.error=err,res=await onError(err,context),isError=!0;else throw err}else if(context.finalized===!1&&onNotFound)res=await onNotFound(context);if(res&&(context.finalized===!1||isError))context.res=res;return context}};var GET_MATCH_RESULT=Symbol();var bufferToFormData=(arrayBuffer,contentType)=>new Response(arrayBuffer,{headers:{"Content-Type":contentType.replace(/^[^;]+/,(mediaType)=>mediaType.toLowerCase())}}).formData();var MAX_NESTING_DEPTH=32,MAX_NESTED_OBJECTS=1e4,isRawRequest=(request)=>("headers"in request),parseBody=async(request,options=Object.create(null))=>{let{all=!1,dot=!1}=options,mediaType=(isRawRequest(request)?request.headers:request.raw.headers).get("Content-Type")?.split(";")[0].trim().toLowerCase();if(mediaType==="multipart/form-data"||mediaType==="application/x-www-form-urlencoded")return parseFormData(request,{all,dot});return{}};async function parseFormData(request,options){if(!isRawRequest(request)&&request.bodyCache.formData)return convertFormDataToBodyData(await request.bodyCache.formData,options);let headers=isRawRequest(request)?request.headers:request.raw.headers,arrayBuffer=await request.arrayBuffer(),formDataPromise=bufferToFormData(arrayBuffer,headers.get("Content-Type")||"");if(!isRawRequest(request))request.bodyCache.formData=formDataPromise;let formData=await formDataPromise;if(formData)return convertFormDataToBodyData(formData,options);return{}}function convertFormDataToBodyData(formData,options){let form=Object.create(null),nestingState={count:0};if(formData.forEach((value,key)=>{if(!(options.all||key.endsWith("[]")))form[key]=value;else handleParsingAllValues(form,key,value)}),options.dot)Object.entries(form).forEach(([key,value])=>{if(key.includes("."))handleParsingNestedValues(form,key,value,nestingState),delete form[key]});return form}var handleParsingAllValues=(form,key,value)=>{if(form[key]!==void 0)if(Array.isArray(form[key]))form[key].push(value);else form[key]=[form[key],value];else if(!key.endsWith("[]"))form[key]=value;else form[key]=[value]},handleParsingNestedValues=(form,key,value,state)=>{if(/(?:^|\.)__proto__\./.test(key))return;let nestedForm=form,keys=key.split(".",MAX_NESTING_DEPTH+2);if(keys.length>MAX_NESTING_DEPTH+1)throwNestingLimitExceeded();keys.forEach((key2,index)=>{if(index===keys.length-1)nestedForm[key2]=value;else{if(!nestedForm[key2]||typeof nestedForm[key2]!=="object"||Array.isArray(nestedForm[key2])||nestedForm[key2]instanceof File){if(state.count++>=MAX_NESTED_OBJECTS)throwNestingLimitExceeded();nestedForm[key2]=Object.create(null)}nestedForm=nestedForm[key2]}})},throwNestingLimitExceeded=()=>{throw Error("Nesting limit exceeded")};var splitPath=(path)=>{let paths=path.split("/");if(paths[0]==="")paths.shift();return paths},splitRoutingPath=(routePath)=>{let{groups,path}=extractGroupsFromPath(routePath),paths=splitPath(path);return replaceGroupMarks(paths,groups)},extractGroupsFromPath=(path)=>{let groups=[];return path=path.replace(/\{[^}]+\}/g,(match,index)=>{let mark=`@${index}`;return groups.push([mark,match]),mark}),{groups,path}},replaceGroupMarks=(paths,groups)=>{for(let i=groups.length-1;i>=0;i--){let[mark]=groups[i];for(let j=paths.length-1;j>=0;j--)if(paths[j].includes(mark)){paths[j]=paths[j].replace(mark,groups[i][1]);break}}return paths},patternCache={},getPattern=(label,next)=>{if(label==="*")return"*";let match=label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);if(match){let cacheKey=`${label}#${next}`;if(!patternCache[cacheKey])if(match[2])patternCache[cacheKey]=next&&next[0]!==":"&&next[0]!=="*"?[cacheKey,match[1],new RegExp(`^${match[2]}(?=/${next})`)]:[label,match[1],new RegExp(`^${match[2]}$`)];else patternCache[cacheKey]=[label,match[1],!0];return patternCache[cacheKey]}return null},tryDecode=(str,decoder)=>{try{return decoder(str)}catch{return str.replace(/(?:%[0-9A-Fa-f]{2})+/g,(match)=>{try{return decoder(match)}catch{return match}})}},tryDecodeURI=(str)=>tryDecode(str,decodeURI),getPath=(request)=>{let url=request.url,start=url.indexOf("/",url.indexOf(":")+4),i=start;for(;i<url.length;i++){let charCode=url.charCodeAt(i);if(charCode===37){let queryIndex=url.indexOf("?",i),hashIndex=url.indexOf("#",i),end=queryIndex===-1?hashIndex===-1?void 0:hashIndex:hashIndex===-1?queryIndex:Math.min(queryIndex,hashIndex),path=url.slice(start,end);return tryDecodeURI(path.includes("%25")?path.replace(/%25/g,"%2525"):path)}else if(charCode===63||charCode===35)break}return url.slice(start,i)};var getPathNoStrict=(request)=>{let result=getPath(request);return result.length>1&&result.at(-1)==="/"?result.slice(0,-1):result},mergePath=(base,sub,...rest)=>{if(rest.length)sub=mergePath(sub,...rest);return`${base?.[0]==="/"?"":"/"}${base}${sub==="/"?"":`${base?.at(-1)==="/"?"":"/"}${sub?.[0]==="/"?sub.slice(1):sub}`}`},checkOptionalParameter=(path)=>{if(path.charCodeAt(path.length-1)!==63||!path.includes(":"))return null;let segments=path.split("/"),results=[],basePath="";return segments.forEach((segment)=>{if(segment!==""&&!/\:/.test(segment))basePath+="/"+segment;else if(/\:/.test(segment))if(segment.charCodeAt(segment.length-1)===63){if(results.length===0&&basePath==="")results.push("/");else results.push(basePath);let optionalSegment=segment.slice(0,-1);basePath+="/"+optionalSegment,results.push(basePath)}else basePath+="/"+segment}),results.filter((v,i,a)=>a.indexOf(v)===i)},tryDecodeURIComponent=(str)=>str.indexOf("%")!==-1?tryDecode(str,decodeURIComponent_):str,_decodeURI=(value)=>{if(value.indexOf("+")!==-1)value=value.replace(/\+/g," ");return tryDecodeURIComponent(value)},_getQueryParam=(url,key,multiple)=>{let hashIndex=url.indexOf("#",8);if(hashIndex!==-1)url=url.slice(0,hashIndex);let encoded;if(!multiple&&key&&key.indexOf("%")===-1&&key.indexOf("+")===-1){let keyIndex2=url.indexOf("?",8);if(keyIndex2===-1)return;if(!url.startsWith(key,keyIndex2+1))keyIndex2=url.indexOf(`&${key}`,keyIndex2+1);while(keyIndex2!==-1){let trailingKeyCode=url.charCodeAt(keyIndex2+key.length+1);if(trailingKeyCode===61){let valueIndex=keyIndex2+key.length+2,endIndex=url.indexOf("&",valueIndex);return _decodeURI(url.slice(valueIndex,endIndex===-1?void 0:endIndex))}else if(trailingKeyCode==38||isNaN(trailingKeyCode))return"";keyIndex2=url.indexOf(`&${key}`,keyIndex2+1)}if(encoded=/[%+]/.test(url),!encoded)return}let results=Object.create(null);encoded??=/[%+]/.test(url);let keyIndex=url.indexOf("?",8);while(keyIndex!==-1){let nextKeyIndex=url.indexOf("&",keyIndex+1),valueIndex=url.indexOf("=",keyIndex);if(valueIndex>nextKeyIndex&&nextKeyIndex!==-1)valueIndex=-1;let name=url.slice(keyIndex+1,valueIndex===-1?nextKeyIndex===-1?void 0:nextKeyIndex:valueIndex);if(encoded)name=_decodeURI(name);if(keyIndex=nextKeyIndex,name==="")continue;let value;if(valueIndex===-1)value="";else if(value=url.slice(valueIndex+1,nextKeyIndex===-1?void 0:nextKeyIndex),encoded)value=_decodeURI(value);if(multiple){if(!(results[name]&&Array.isArray(results[name])))results[name]=[];results[name].push(value)}else results[name]??=value}return key?results[key]:results},getQueryParam=_getQueryParam,getQueryParams=(url,key)=>_getQueryParam(url,key,!0),decodeURIComponent_=decodeURIComponent;var HonoRequest=class{raw;#validatedData;#matchResult;routeIndex=0;path;bodyCache={};constructor(request,path="/",matchResult=[[]]){this.raw=request,this.path=path,this.#matchResult=matchResult}param(key){return key?this.#getDecodedParam(key):this.#getAllDecodedParams()}#getDecodedParam(key){let paramKey=this.#matchResult[0][this.routeIndex]?.[1][key],param=this.#getParamValue(paramKey);return param&&tryDecodeURIComponent(param)}#getAllDecodedParams(){let decoded={},keys=Object.keys(this.#matchResult[0][this.routeIndex]?.[1]??{});for(let key of keys){let value=this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);if(value!==void 0)decoded[key]=tryDecodeURIComponent(value)}return decoded}#getParamValue(paramKey){return this.#matchResult[1]?this.#matchResult[1][paramKey]:paramKey}query(key){return getQueryParam(this.url,key)}queries(key){return getQueryParams(this.url,key)}header(name){if(name)return this.raw.headers.get(name)??void 0;let headerData=Object.create(null);return this.raw.headers.forEach((value,key)=>{headerData[key]=value}),headerData}async parseBody(options){return parseBody(this,options)}#cachedBody=(key)=>{let{bodyCache,raw}=this,cachedBody=bodyCache[key];if(cachedBody)return cachedBody;for(let anyCachedKey in bodyCache)return bodyCache[anyCachedKey].then((body)=>{if(anyCachedKey==="json")body=JSON.stringify(body);return new Response(body)[key]()});return bodyCache[key]=raw[key]()};json(){return this.#cachedBody("text").then((text)=>JSON.parse(text))}text(){return this.#cachedBody("text")}arrayBuffer(){return this.#cachedBody("arrayBuffer")}bytes(){return this.#cachedBody("arrayBuffer").then((buffer)=>new Uint8Array(buffer))}blob(){return this.#cachedBody("blob")}formData(){return this.#cachedBody("formData")}addValidatedData(target,data){(this.#validatedData??={})[target]=data}valid(target){return this.#validatedData?.[target]}get url(){return this.raw.url}get method(){return this.raw.method}get[GET_MATCH_RESULT](){return this.#matchResult}get matchedRoutes(){return this.#matchResult[0].map(([[,route]])=>route)}get routePath(){return this.#matchResult[0].map(([[,route]])=>route)[this.routeIndex].path}};var HtmlEscapedCallbackPhase={Stringify:1,BeforeStream:2,Stream:3},raw=(value,callbacks)=>{let escapedString=new String(value);return escapedString.isEscaped=!0,escapedString.callbacks=callbacks,escapedString};var resolveCallback=async(str,phase,preserveCallbacks,context,buffer)=>{if(typeof str==="object"&&!(str instanceof String)){if(!(str instanceof Promise))str=str.toString();if(str instanceof Promise)str=await str}let callbacks=str.callbacks;if(!callbacks?.length)return Promise.resolve(str);if(buffer)buffer[0]+=str;else buffer=[str];let resStr=Promise.all(callbacks.map((c)=>c({phase,buffer,context}))).then((res)=>Promise.all(res.filter(Boolean).map((str2)=>resolveCallback(str2,phase,!1,context,buffer))).then(()=>buffer[0]));if(preserveCallbacks)return raw(await resStr,callbacks);else return resStr};var TEXT_PLAIN="text/plain; charset=UTF-8",setDefaultContentType=(contentType,headers)=>({"Content-Type":contentType,...headers}),createResponseInstance=(body,init)=>new Response(body,init),Context=class{#rawRequest;#req;env={};#var;finalized=!1;error;#status;#executionCtx;#res;#layout;#renderer;#notFoundHandler;#preparedHeaders;#matchResult;#path;constructor(req,options){if(this.#rawRequest=req,options)this.#executionCtx=options.executionCtx,this.env=options.env,this.#notFoundHandler=options.notFoundHandler,this.#path=options.path,this.#matchResult=options.matchResult}get req(){return this.#req??=new HonoRequest(this.#rawRequest,this.#path,this.#matchResult),this.#req}get event(){if(this.#executionCtx&&"respondWith"in this.#executionCtx)return this.#executionCtx;else throw Error("This context has no FetchEvent")}get executionCtx(){if(this.#executionCtx)return this.#executionCtx;else throw Error("This context has no ExecutionContext")}get res(){return this.#res||=createResponseInstance(null,{headers:this.#preparedHeaders??=new Headers})}set res(_res){if(this.#res&&_res){_res=createResponseInstance(_res.body,_res);for(let[k,v]of this.#res.headers.entries()){if(k==="content-type")continue;if(k==="set-cookie"){let cookies=this.#res.headers.getSetCookie();_res.headers.delete("set-cookie");for(let cookie of cookies)_res.headers.append("set-cookie",cookie)}else _res.headers.set(k,v)}}this.#res=_res,this.finalized=!0}render=(...args)=>(this.#renderer??=(content)=>this.html(content),this.#renderer(...args));setLayout=(layout)=>this.#layout=layout;getLayout=()=>this.#layout;setRenderer=(renderer)=>{this.#renderer=renderer};header=(name,value,options)=>{if(this.finalized)this.#res=createResponseInstance(this.#res.body,this.#res);let headers=this.#res?this.#res.headers:this.#preparedHeaders??=new Headers;if(value===void 0)headers.delete(name);else if(options?.append)headers.append(name,value);else headers.set(name,value)};status=(status)=>{this.#status=status};set=(key,value)=>{this.#var??=new Map,this.#var.set(key,value)};get=(key)=>this.#var?this.#var.get(key):void 0;get var(){if(!this.#var)return{};return Object.fromEntries(this.#var)}#newResponse(data,arg,headers){let responseHeaders=this.#res?new Headers(this.#res.headers):this.#preparedHeaders;if(typeof arg==="object"&&arg.headers){responseHeaders??=new Headers;for(let[key,value]of new Headers(arg.headers))if(key==="set-cookie")responseHeaders.append(key,value);else responseHeaders.set(key,value)}if(headers){if(!responseHeaders){let count=0;for(let k in headers)if(++count>1||typeof headers[k]!=="string"){responseHeaders=new Headers;break}}if(responseHeaders)for(let k in headers){let v=headers[k];if(typeof v==="string")responseHeaders.set(k,v);else{responseHeaders.delete(k);for(let v2 of v)responseHeaders.append(k,v2)}}}let status=typeof arg==="number"?arg:arg?.status??this.#status;return createResponseInstance(data,{status,headers:responseHeaders??headers})}newResponse=(...args)=>this.#newResponse(...args);body=(data,arg,headers)=>this.#newResponse(data,arg,headers);text=(text,arg,headers)=>!this.#preparedHeaders&&!this.#status&&!arg&&!headers&&!this.finalized?new Response(text):this.#newResponse(text,arg,setDefaultContentType(TEXT_PLAIN,headers));json=(object,arg,headers)=>this.#newResponse(JSON.stringify(object),arg,setDefaultContentType("application/json",headers));html=(html,arg,headers)=>{let res=(html2)=>this.#newResponse(html2,arg,setDefaultContentType("text/html; charset=UTF-8",headers));return typeof html==="object"?resolveCallback(html,HtmlEscapedCallbackPhase.Stringify,!1,{}).then(res):res(html)};redirect=(location,status)=>{let locationString=String(location);return this.header("Location",!/[^\x00-\xFF]/.test(locationString)?locationString:encodeURI(locationString)),this.newResponse(null,status??302)};notFound=()=>(this.#notFoundHandler??=()=>createResponseInstance(),this.#notFoundHandler(this))};var METHOD_NAME_ALL="ALL",METHOD_NAME_ALL_LOWERCASE="all",METHODS=["get","post","put","delete","options","patch","query"],MESSAGE_MATCHER_IS_ALREADY_BUILT="Can not add a route since the matcher is already built.",UnsupportedPathError=class extends Error{};var COMPOSED_HANDLER="__COMPOSED_HANDLER";var notFoundHandler=(c)=>c.text("404 Not Found",404),errorHandler=(err,c)=>{if("getResponse"in err){let res=err.getResponse();return c.newResponse(res.body,res)}return console.error(err),c.text("Internal Server Error",500)},Hono=class _Hono{get;post;put;delete;options;patch;query;all;on;use;router;getPath;_basePath="/";#path="/";routes=[];constructor(options={}){[...METHODS,METHOD_NAME_ALL_LOWERCASE].forEach((method)=>{this[method]=(args1,...args)=>{if(typeof args1==="string")this.#path=args1;else this.#addRoute(method,this.#path,args1);return args.forEach((handler)=>{this.#addRoute(method,this.#path,handler)}),this}}),this.on=(method,path,...handlers)=>{for(let p of[path].flat()){this.#path=p;for(let m of[method].flat())handlers.map((handler)=>{this.#addRoute(m.toUpperCase(),this.#path,handler)})}return this},this.use=(arg1,...handlers)=>{if(typeof arg1==="string")this.#path=arg1;else this.#path="*",handlers.unshift(arg1);return handlers.forEach((handler)=>{this.#addRoute(METHOD_NAME_ALL,this.#path,handler)}),this};let{strict,...optionsWithoutStrict}=options;Object.assign(this,optionsWithoutStrict),this.getPath=strict??!0?options.getPath??getPath:getPathNoStrict}#clone(){let clone=new _Hono({router:this.router,getPath:this.getPath});return clone.errorHandler=this.errorHandler,clone.#notFoundHandler=this.#notFoundHandler,clone.routes=this.routes,clone}#notFoundHandler=notFoundHandler;errorHandler=errorHandler;route(path,app){let subApp=this.basePath(path);return app.routes.map((r)=>{let handler;if(app.errorHandler===errorHandler)handler=r.handler;else handler=async(c,next)=>(await compose([],app.errorHandler)(c,()=>r.handler(c,next))).res,handler[COMPOSED_HANDLER]=r.handler;subApp.#addRoute(r.method,r.path,handler,r.basePath)}),this}basePath(path){let subApp=this.#clone();return subApp._basePath=mergePath(this._basePath,path),subApp}onError=(handler)=>(this.errorHandler=handler,this);notFound=(handler)=>(this.#notFoundHandler=handler,this);mount(path,applicationHandler,options){let replaceRequest,optionHandler;if(options)if(typeof options==="function")optionHandler=options;else if(optionHandler=options.optionHandler,options.replaceRequest===!1)replaceRequest=(request)=>request;else replaceRequest=options.replaceRequest;let getOptions=optionHandler?(c)=>{let options2=optionHandler(c);return Array.isArray(options2)?options2:[options2]}:(c)=>{let executionContext=void 0;try{executionContext=c.executionCtx}catch{}return[c.env,executionContext]};replaceRequest||=(()=>{let mergedPath=mergePath(this._basePath,path),pathPrefixLength=mergedPath==="/"?0:mergedPath.length;return(request)=>{let url=new URL(request.url);return url.pathname=this.getPath(request).slice(pathPrefixLength)||"/",new Request(url,request)}})();let handler=async(c,next)=>{let res=await applicationHandler(replaceRequest(c.req.raw),...getOptions(c));if(res)return res;await next()};return this.#addRoute(METHOD_NAME_ALL,mergePath(path,"*"),handler),this}#addRoute(method,path,handler,baseRoutePath){method=method.toUpperCase(),path=mergePath(this._basePath,path);let r={basePath:baseRoutePath!==void 0?mergePath(this._basePath,baseRoutePath):this._basePath,path,method,handler};this.router.add(method,path,[handler,r]),this.routes.push(r)}#handleError(err,c){if(err instanceof Error)return this.errorHandler(err,c);throw err}#dispatch(request,executionCtx,env,method){if(method==="HEAD")return(async()=>new Response(null,await this.#dispatch(request,executionCtx,env,"GET")))();let path=this.getPath(request,{env}),matchResult=this.router.match(method,path),c=new Context(request,{path,matchResult,env,executionCtx,notFoundHandler:this.#notFoundHandler});if(matchResult[0].length===1){let res;try{res=matchResult[0][0][0][0](c,async()=>{c.res=await this.#notFoundHandler(c)})}catch(err){return this.#handleError(err,c)}return res instanceof Promise?res.then((resolved)=>resolved||(c.finalized?c.res:this.#notFoundHandler(c))).catch((err)=>this.#handleError(err,c)):res??this.#notFoundHandler(c)}let composed=compose(matchResult[0],this.errorHandler,this.#notFoundHandler);return(async()=>{try{let context=await composed(c);if(!context.finalized)throw Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");return context.res}catch(err){return this.#handleError(err,c)}})()}fetch=(request,...rest)=>this.#dispatch(request,rest[1],rest[0],request.method);request=(input,requestInit,Env,executionCtx)=>{if(input instanceof Request)return this.fetch(requestInit?new Request(input,requestInit):input,Env,executionCtx);return input=input.toString(),this.fetch(new Request(/^https?:\/\//.test(input)?input:`http://localhost${mergePath("/",input)}`,requestInit),Env,executionCtx)};fire=()=>{addEventListener("fetch",(event)=>{event.respondWith(this.#dispatch(event.request,event,void 0,event.request.method))})}};var createNullObject=()=>Object.create(null);var emptyParam=[];function match(method,path){let matchers=this.buildAllMatchers(),match2=(method2,path2)=>{let matcher=matchers[method2]||matchers[METHOD_NAME_ALL],staticMatch=matcher[2][path2];if(staticMatch)return staticMatch;let match3=path2.match(matcher[0]);if(!match3)return[[],emptyParam];let index=match3.indexOf("",1);return[matcher[1][index],match3]};return this.match=match2,match2(method,path)}var LABEL_REG_EXP_STR="[^/]+",ONLY_WILDCARD_REG_EXP_STR=".*",TAIL_WILDCARD_REG_EXP_STR="(?:|/.*)",PATH_ERROR=Symbol(),regExpMetaChars=new Set(".\\+*[^]$()");function compareKey(a,b){if(a.length===1)return b.length===1?a<b?-1:1:-1;if(b.length===1)return 1;if(a===ONLY_WILDCARD_REG_EXP_STR||a===TAIL_WILDCARD_REG_EXP_STR)return b===TAIL_WILDCARD_REG_EXP_STR?-1:1;else if(b===ONLY_WILDCARD_REG_EXP_STR||b===TAIL_WILDCARD_REG_EXP_STR)return-1;if(a===LABEL_REG_EXP_STR)return 1;else if(b===LABEL_REG_EXP_STR)return-1;return a.length===b.length?a<b?-1:1:b.length-a.length}var Node=class _Node{#index;#varIndex;#children=createNullObject();insert(tokens,index,paramMap,context,isStatic){let node=this;for(let i=0,len=tokens.length;i<len;i++){let token=tokens[i],pattern=token.length===1?token==="*"?i===len-1?["","",ONLY_WILDCARD_REG_EXP_STR]:["","",LABEL_REG_EXP_STR]:null:token==="/*"?["","",TAIL_WILDCARD_REG_EXP_STR]:token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/),nextNode;if(pattern){let name=pattern[1],regexpStr=pattern[2]||LABEL_REG_EXP_STR;if(name&&pattern[2]){if(regexpStr===".*")throw PATH_ERROR;if(regexpStr=regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/,"(?:"),/\((?!\?:)/.test(regexpStr))throw PATH_ERROR;if(regexpStr.length===1&®ExpMetaChars.has(regexpStr))throw PATH_ERROR}if(nextNode=node.#children[regexpStr],!nextNode){if(regexpStr!==ONLY_WILDCARD_REG_EXP_STR&®expStr!==TAIL_WILDCARD_REG_EXP_STR){for(let k in node.#children)if((regexpStr.length>1||k.length>1)&&k!==ONLY_WILDCARD_REG_EXP_STR&&k!==TAIL_WILDCARD_REG_EXP_STR)throw PATH_ERROR}nextNode=node.#children[regexpStr]=new _Node}if(name!=="")nextNode.#varIndex??=context.varIndex++,paramMap.push([name,nextNode.#varIndex])}else if(nextNode=node.#children[token],!nextNode){for(let k in node.#children)if(k.length>1&&k!==ONLY_WILDCARD_REG_EXP_STR&&k!==TAIL_WILDCARD_REG_EXP_STR)throw PATH_ERROR;nextNode=node.#children[token]=new _Node}node=nextNode}if(node.#index!==void 0)throw PATH_ERROR;node.#index=isStatic?-1:index}buildRegExpStr(){let strList=Object.keys(this.#children).sort(compareKey).map((k)=>{let c=this.#children[k],childStr=c.buildRegExpStr();return childStr===""?"":(typeof c.#varIndex==="number"?`(${k})@${c.#varIndex}`:regExpMetaChars.has(k)?`\\${k}`:k)+childStr}).filter(Boolean);if(typeof this.#index==="number"&&this.#index!==-1)strList.unshift(`#${this.#index}`);if(strList.length===0)return"";if(strList.length===1)return strList[0];return"(?:"+strList.join("|")+")"}};var Trie=class{#context={varIndex:0};#root=new Node;#index=0;paths=createNullObject();insert(path,isStatic){if(isStatic){this.#root.insert(path.split(""),0,[],this.#context,!0);return}let paramAssoc=[],groups=[],markedPath=path;for(let i=0;;){let replaced=!1;if(markedPath=markedPath.replace(/\{[^}]+\}/g,(m)=>{let mark=`@\\${i}`;return groups[i]=[mark,m],i++,replaced=!0,mark}),!replaced)break}let tokens=markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g)||[];for(let i=groups.length-1;i>=0;i--){let[mark]=groups[i];for(let j=tokens.length-1;j>=0;j--)if(tokens[j].indexOf(mark)!==-1){tokens[j]=tokens[j].replace(mark,groups[i][1]);break}}this.#root.insert(tokens,this.#index,paramAssoc,this.#context,!1),this.paths[path]=[this.#index++,paramAssoc]}buildRegExp(){let regexp=this.#root.buildRegExpStr();if(regexp==="")return[/^$/,[],[]];let captureIndex=0,indexReplacementMap=[],paramReplacementMap=[];return regexp=regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g,(_,handlerIndex,paramIndex)=>{if(handlerIndex!==void 0)return indexReplacementMap[++captureIndex]=Number(handlerIndex),"$()";if(paramIndex!==void 0)return paramReplacementMap[Number(paramIndex)]=++captureIndex,"";return""}),[new RegExp(`^${regexp}`),indexReplacementMap,paramReplacementMap]}};var wildcardRegExpCache=createNullObject();function buildWildcardRegExp(path){return wildcardRegExpCache[path]??=new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g,(match2,metaChar)=>metaChar?`\\${metaChar}`:match2==="/*"?TAIL_WILDCARD_REG_EXP_STR:match2==="*"?ONLY_WILDCARD_REG_EXP_STR:`/:${LABEL_REG_EXP_STR}`)}$`)}function findMiddleware(middleware,path){for(let k of Object.keys(middleware).sort((a,b)=>b.length-a.length))if(buildWildcardRegExp(k).test(path))return[...middleware[k]];return}var RegExpRouter=class{name="RegExpRouter";#middleware;#routes;#tries;constructor(){this.#middleware={[METHOD_NAME_ALL]:createNullObject()},this.#routes={[METHOD_NAME_ALL]:createNullObject()},this.#tries={[METHOD_NAME_ALL]:new Trie}}#insertPath(method,path){try{this.#tries[method].insert(path,!/\*|\/:/.test(path))}catch(e){throw e===PATH_ERROR?new UnsupportedPathError(path):e}}add(method,path,handler){let middleware=this.#middleware,routes=this.#routes;if(!middleware)throw Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);if(!middleware[method]){this.#tries[method]=new Trie;for(let handlerMap of[middleware,routes]){handlerMap[method]=createNullObject();for(let p in handlerMap[METHOD_NAME_ALL])handlerMap[method][p]=[...handlerMap[METHOD_NAME_ALL][p]],this.#insertPath(method,p)}}if(path==="/*")path="*";let methods=method===METHOD_NAME_ALL?Object.keys(middleware):[method];if(/\*$/.test(path)){let re=buildWildcardRegExp(path);for(let m of methods)if(!middleware[m][path])this.#insertPath(m,path),middleware[m][path]=findMiddleware(middleware[m],path)||findMiddleware(middleware[METHOD_NAME_ALL],path)||[];for(let handlerMap of[middleware,routes])for(let m of methods)for(let p in handlerMap[m])re.test(p)&&handlerMap[m][p].push([handler,path]);return}let paths=checkOptionalParameter(path)||[path];for(let path2 of paths)for(let m of methods){if(!routes[m][path2])this.#insertPath(m,path2),routes[m][path2]=findMiddleware(middleware[m],path2)||findMiddleware(middleware[METHOD_NAME_ALL],path2)||[];routes[m][path2].push([handler,path2])}}match=match;buildAllMatchers(){let matchers=createNullObject();for(let method of Object.keys(this.#routes))matchers[method]=this.#buildMatcher(method);return this.#middleware=this.#routes=this.#tries=void 0,wildcardRegExpCache=createNullObject(),matchers}#buildMatcher(method){let middleware=this.#middleware[method],routes=this.#routes[method],trie=this.#tries[method],staticMap=createNullObject(),handlerData=[],[regexp,indexReplacementMap,paramReplacementMap]=trie.buildRegExp();for(let r of[middleware,routes])for(let path in r){let handlers=r[path],pathData=trie.paths[path];if(!pathData){staticMap[path]=[handlers.map(([h])=>[h,createNullObject()]),emptyParam];continue}handlerData[pathData[0]]=handlers.map(([h,handlerPath])=>[h,trie.paths[handlerPath][1].reduceRight((map,[key],i)=>(map[key]=paramReplacementMap[pathData[1][i][1]],map),createNullObject())])}return[regexp,indexReplacementMap.map((i)=>handlerData[i]),staticMap]}};var SmartRouter=class{name="SmartRouter";#routers=[];#routes=[];constructor(init){this.#routers=init.routers}add(method,path,handler){if(!this.#routes)throw Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);this.#routes.push([method,path,handler])}match(method,path){if(!this.#routes)throw Error("Fatal error");let routers=this.#routers,routes=this.#routes,len=routers.length,i=0,res;for(;i<len;i++){let router=routers[i];try{for(let i2=0,len2=routes.length;i2<len2;i2++)router.add(...routes[i2]);res=router.match(method,path)}catch(e){if(e instanceof UnsupportedPathError)continue;throw e}this.match=router.match.bind(router),this.#routers=[router],this.#routes=void 0;break}if(i===len)throw Error("Fatal error");return this.name=`SmartRouter + ${this.activeRouter.name}`,res}get activeRouter(){if(this.#routes||this.#routers.length!==1)throw Error("No active router has been determined yet.");return this.#routers[0]}};var emptyParams=createNullObject(),order=0,Node2=class _Node{#methods=[];#children=createNullObject();#patterns=[];#pattern;#params=emptyParams;insert(method,path,handler){let curNode=this,parts=splitRoutingPath(path),possibleKeys=new Set,i=0;for(let p of parts){let nextP=parts[++i],pattern=getPattern(p,nextP)||(nextP===void 0&&p&&p.indexOf("*")===p.length-1?p:null),isParam=Array.isArray(pattern),key=isParam?pattern[0]:pattern||p,child=curNode.#children[key]||=new _Node;if(pattern&&!child.#pattern)child.#pattern=pattern,curNode.#patterns.push(child);if(curNode=child,isParam)possibleKeys.add(pattern[1])}curNode.#methods.push({[method]:{handler,possibleKeys:[...possibleKeys],score:++order}})}#pushHandlerSets(handlerSets,node,method,nodeParams,params){for(let i=0,len=node.#methods.length;i<len;i++){let m=node.#methods[i],handlerSet=m[method]||m[METHOD_NAME_ALL];if(handlerSet){handlerSet.params=createNullObject(),handlerSets.push(handlerSet);for(let i2=0,len2=handlerSet.possibleKeys.length;i2<len2;i2++){let key=handlerSet.possibleKeys[i2];handlerSet.params[key]=params?.[key]&&!i2?params[key]:nodeParams[key]??params?.[key]}}}}search(method,path){let handlerSets=[];this.#params=emptyParams;let curNodes=[this],parts=splitPath(path),curNodesQueue=[],len=parts.length,partOffsets=null;for(let i=0;i<len;i++){let part=parts[i],isLast=i===len-1,tempNodes=[];for(let j=0,len2=curNodes.length;j<len2;j++){let node=curNodes[j],nextNode=node.#children[part];if(nextNode)if(nextNode.#params=node.#params,isLast){if(nextNode.#children["*"])this.#pushHandlerSets(handlerSets,nextNode.#children["*"],method,node.#params);this.#pushHandlerSets(handlerSets,nextNode,method,node.#params)}else tempNodes.push(nextNode);for(let child of node.#patterns){let pattern=child.#pattern,params=node.#params===emptyParams?{}:{...node.#params};if(typeof pattern==="string"){if(pattern==="*"||part.startsWith(pattern.slice(0,-1))){if(this.#pushHandlerSets(handlerSets,child,method,node.#params),pattern==="*")child.#params=params,tempNodes.push(child)}continue}let[,name,matcher]=pattern;if(!part&&matcher===!0)continue;if(matcher!==!0){if(!partOffsets){partOffsets=[];let offset=path[0]==="/"?1:0;for(let p=0;p<len;p++)partOffsets[p]=offset,offset+=parts[p].length+1}let restPathString=path.slice(partOffsets[i]),m=matcher.exec(restPathString);if(m){if(params[name]=m[0],this.#pushHandlerSets(handlerSets,child,method,node.#params,params),m[0].length===restPathString.length&&child.#children["*"])this.#pushHandlerSets(handlerSets,child.#children["*"],method,node.#params,params);for(let _ in child.#children){child.#params=params;let componentCount=m[0].match(/\//g)?.length??0;(curNodesQueue[componentCount]||=[]).push(child);break}continue}}if(matcher===!0||matcher.test(part))if(params[name]=part,isLast){if(this.#pushHandlerSets(handlerSets,child,method,params,node.#params),child.#children["*"])this.#pushHandlerSets(handlerSets,child.#children["*"],method,params,node.#params)}else child.#params=params,tempNodes.push(child)}}let shifted=curNodesQueue.shift();curNodes=shifted?tempNodes.concat(shifted):tempNodes}if(handlerSets[1])handlerSets.sort((a,b)=>a.score-b.score);return[handlerSets.map(({handler,params})=>[handler,params])]}};var TrieRouter=class{name="TrieRouter";#node=new Node2;add(method,path,handler){for(let result of checkOptionalParameter(path)||[path])this.#node.insert(method,result,handler)}match(method,path){return this.#node.search(method,path)}};var Hono2=class extends Hono{constructor(options={}){super(options);this.router=options.router??new SmartRouter({routers:[new RegExpRouter,new TrieRouter]})}};import{AsyncLocalStorage}from"async_hooks";var ACTIVITY_LOG_CAP=250,GROUP_DETAIL_CAP=20,actorStorage=new AsyncLocalStorage;function withActor(actor,fn){return actorStorage.run(actor,fn)}var groupStorage=new AsyncLocalStorage;async function collectActivity(fn){let ops=[];return{value:await groupStorage.run(ops,fn),ops}}function emitGroupedActivity(ctx,verb,ops){if(ops.length===0)return;let first=ops[0];publish(ctx,verb,first.target,{ops:ops.slice(0,GROUP_DETAIL_CAP).map((op)=>({...op,target:enrichBoards(ctx,op.target)})),opCount:ops.length})}function emitActivity(ctx,verb,target){let group=groupStorage.getStore();if(group){group.push({verb,target});return}publish(ctx,verb,target,{})}async function tracked(ctx,verb,target,run){let result=await run();if(result.ok)emitActivity(ctx,verb,typeof target==="function"?target(result.value):target);return result}var logs=new Map,nextId=1;function activityLog(folderRoot){return logs.get(folderRoot)??[]}function enrichBoards(ctx,target){if(!target.screenId||target.boardId||!ctx.folder.boards)return target;let hosting=[...ctx.folder.boards.values()].filter((b)=>b.frames.some((f)=>f.screen===target.screenId)),live=hosting.filter((b)=>!isArchived(b)),boards=(live.length>0?live:hosting).map((b)=>b.id);return boards.length>0?{...target,boards}:target}function publish(ctx,verb,target,extra){let actor=actorStorage.getStore(),event={type:"activity",id:nextId++,ts:Date.now(),verb,source:actor?.source??"cli",...actor?.session?{session:actor.session}:{},target:enrichBoards(ctx,target),...extra},root=ctx.folder.root,log=logs.get(root)??[];if(log.push(event),log.length>ACTIVITY_LOG_CAP)log.splice(0,log.length-ACTIVITY_LOG_CAP);logs.set(root,log),ctx.broadcast(event)}import{readFileSync as readFileSync4}from"fs";import{readFile as readFile9}from"fs/promises";import{homedir as homedir4}from"os";import{join as join16}from"path";import{readFileSync as readFileSync3}from"fs";import{readFile as readFile8}from"fs/promises";async function currentToken(cloud){if(cloud.resolveToken)try{return await cloud.resolveToken()??cloud.token}catch{return cloud.token}return cloud.token}var LOGGED_OUT={loggedIn:!1,verified:null,login:{state:"idle"}};function signInRequired(access,message){return Object.assign(Error(message),{signInRequired:access})}function asSignInRequired(error){if(!error||typeof error!=="object")return null;let marker=error.signInRequired;return marker==="signed-out"||marker==="expired"?marker:null}function boardLimitReached(limit,message){return Object.assign(Error(message),{boardLimit:limit})}function asBoardLimit(error){if(!error||typeof error!=="object")return null;let marker=error.boardLimit;if(!marker||typeof marker.tier!=="string"||typeof marker.limit!=="number")return null;return{tier:marker.tier,limit:marker.limit}}var LOOPBACK_CLOUD_HOSTS=new Set(["localhost","127.0.0.1","::1","[::1]"]);function isSecureCloudUrl(cloudUrl){try{let u=new URL(cloudUrl);if(u.protocol==="https:")return!0;return u.protocol==="http:"&&LOOPBACK_CLOUD_HOSTS.has(u.hostname)}catch{return!1}}var SharedCacheSchema=object({version:literal(1),folderId:string().min(1),threads:array(CommentThreadSchema),links:record(string(),_enum(["ok","revoked"])),refreshedAt:exports_iso.datetime()}),OwnerFeedSchema=object({threads:array(CommentThreadSchema),links:record(string(),_enum(["ok","revoked"])),now:exports_iso.datetime()}),FETCH_TIMEOUT_MS=1e4;async function cloudFailure2(response){let body=await response.json().catch(()=>{return});if(body&&typeof body==="object"&&"message"in body){let{message,error}=body;if(typeof message==="string"&&message)return isCloudErrorCode(error)?`${message} (${error})`:message}return`Cloud comment request failed (${response.status}).`}function signature(threads){return threads.map((thread)=>`${thread.id}:${thread.updatedAt}:${thread.status}:${thread.messages.length}`).sort().join("|")}class SharedCommentsClient{folderId;cloud;cachePath;tail=Promise.resolve();constructor(folderId,cloud,cachePath){this.folderId=folderId;this.cloud=cloud;this.cachePath=cachePath}async cached(){let id=this.folderId();if(!id)return[];try{let parsed=SharedCacheSchema.parse(JSON.parse(await readFile8(this.cachePath(),"utf8")));return parsed.folderId===id?parsed.threads:[]}catch{return[]}}cachedSync(){let id=this.folderId();if(!id)return[];try{let parsed=SharedCacheSchema.parse(JSON.parse(readFileSync3(this.cachePath(),"utf8")));return parsed.folderId===id?parsed.threads:[]}catch{return[]}}refresh(){let run=this.tail.then(()=>this.doRefresh(),()=>this.doRefresh());return this.tail=run.then(()=>{return},()=>{return}),run}async doRefresh(){let folderId=this.folderId(),before=await this.cached();if(!folderId)return{status:"unpublished",changed:!1,boardIds:[],threads:before};let token=await currentToken(this.cloud);if(!token)return{status:"logged-out",changed:!1,boardIds:[],threads:before};try{let query=new URLSearchParams({folderId,status:"all"}),response=await fetch(`${this.cloud.url}/v1/comment-threads?${query}`,{headers:{authorization:`Bearer ${token}`},signal:AbortSignal.timeout(FETCH_TIMEOUT_MS)});if(!response.ok)throw Error(`shared comment fetch failed (${response.status})`);let payload=OwnerFeedSchema.parse(await response.json()),changed=signature(before)!==signature(payload.threads);await writeJsonAtomic(this.cachePath(),{version:1,folderId,threads:payload.threads,links:payload.links,refreshedAt:payload.now});let boardIds=changed?[...new Set([...before,...payload.threads].map((thread)=>thread.boardId))]:[];return{status:"ok",changed,boardIds,threads:payload.threads}}catch{return{status:"offline",changed:!1,boardIds:[],threads:before}}}async get(id){return(await this.refresh()).threads.find((thread)=>thread.id===id)}async mutate(path,init){let token=await currentToken(this.cloud);if(!token)throw Error("Sign in to write shared comments.");let response=await fetch(`${this.cloud.url}${path}`,{...init,headers:{"content-type":"application/json",authorization:`Bearer ${token}`,...init.headers},signal:AbortSignal.timeout(FETCH_TIMEOUT_MS)});if(!response.ok)throw Error(await cloudFailure2(response));let parsed=object({thread:CommentThreadSchema}).parse(await response.json());return await this.refresh(),parsed.thread}create(slug,input){return this.mutate(`/v1/links/${encodeURIComponent(slug)}/comment-threads`,{method:"POST",body:JSON.stringify(input)})}reply(id,body,authorKind){return this.mutate(`/v1/comment-threads/${encodeURIComponent(id)}/messages`,{method:"POST",body:JSON.stringify({body,authorKind})})}setResolved(id,resolved){return this.mutate(`/v1/comment-threads/${encodeURIComponent(id)}`,{method:"PATCH",body:JSON.stringify({resolved})})}deleteMessage(id,messageId){let path=`/v1/comment-threads/${encodeURIComponent(id)}/messages/${encodeURIComponent(messageId)}`;return this.mutate(path,{method:"DELETE"})}}function pathAt(root,path){let node=root;for(let idx of path){if(!node||!isComponentNode(node))return null;if(!node.children||idx<0||idx>=node.children.length)return null;node=node.children[idx]}return node??null}function parentOf(path){if(path.length===0)return null;let last=path[path.length-1];return{parent:path.slice(0,-1),index:last}}function isIdLocator(value){return typeof value==="string"&&value.startsWith("@")&&value.length>1}function findById(root,id){function walk(node,path){if(nodeId(node)===id)return path;if(isSnippetInstance(node))return null;if(!isComponentNode(node))return null;if(!node.children)return null;for(let i=0;i<node.children.length;i++){let child=node.children[i];if(!child)continue;let found=walk(child,[...path,i]);if(found)return found}return null}return walk(root,[])}function resolveLocator(root,locator){let loc=locator;if(typeof loc==="string"&&loc.trim().startsWith("[")&&loc.trim().endsWith("]"))try{let parsed=JSON.parse(loc.trim());if(Array.isArray(parsed)&&parsed.every((n)=>Number.isInteger(n)&&n>=0))loc=parsed}catch{}if(Array.isArray(loc))return pathAt(root,loc)===null?null:loc;if(isIdLocator(loc))return findById(root,loc.slice(1));return null}var LocalCommentsFileSchema=object({version:literal(1),folderId:string().min(1),threads:array(CommentThreadSchema)});class CommentStoreError extends Error{code;constructor(code,message){super(message);this.code=code}}function newestPublishedSlot(slots,boardId){return slots.filter((slot)=>slot.latestVersionId!==null&&slot.context.boardIds.includes(boardId)).sort((left,right)=>{let leftAt=left.lastPublishedAt?Date.parse(left.lastPublishedAt):0;return(right.lastPublishedAt?Date.parse(right.lastPublishedAt):0)-leftAt||left.slug.localeCompare(right.slug)})[0]}var defaultAuthor=()=>({kind:"user"});function ownerAuthorKind(author){return author?.kind==="agent"?"agent":"user"}function folderIdentity(ctx){return ctx.folder.config.folderId??Bun.CryptoHasher.hash("sha256",ctx.folder.root,"hex")}function localCommentsPath(folderId){let override=process.env.VELLOO_COMMENTS_PATH;if(override)return override;let safe=folderId.replace(/[^A-Za-z0-9_-]/g,"_");return join16(process.env.VELLOO_COMMENTS_DIR??join16(homedir4(),".velloo","comments"),`${safe}.json`)}function message(body,author,now){return{id:crypto.randomUUID(),author:CommentAuthorSchema.parse(author),body:body.trim(),createdAt:now}}class LocalCommentsService{ctxFor;storePath;publish;tail=Promise.resolve();shared;constructor(ctxFor,storePath,cloud,publish){this.ctxFor=ctxFor;this.storePath=storePath;this.publish=publish;if(cloud)this.shared=new SharedCommentsClient(()=>this.ctxFor().folder.config.folderId,cloud,()=>`${this.path()}.shared`)}async cloudAvailability(boardId){if(!this.shared||!this.publish)return{available:!1,reason:"unsupported"};let access=await this.publish.access().catch(()=>({state:"signed-out"}));if(access.state!=="ready")return{available:!1,reason:access.state};let slots=await this.publish.destinations().then((value)=>value.slots).catch(()=>[]),slot=newestPublishedSlot(slots,boardId);if(!slot)return{available:!1,reason:"unpublished"};return{available:!0,slug:slot.slug,url:slot.url}}async cloudTarget(boardId){if(!this.shared||!this.publish)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let access=await this.publish.access().catch(()=>({state:"signed-out"}));if(access.state!=="ready")throw new CommentStoreError("invalid",access.state==="expired"?"Your velloo-cloud session ended \u2014 sign in again to write cloud comments.":"Sign in to velloo cloud to write cloud comments.");let slots=await this.publish.destinations().then((value)=>value.slots).catch(()=>[]),slot=newestPublishedSlot(slots,boardId);if(!slot?.latestVersionId)throw new CommentStoreError("invalid","Publish this board before commenting on the cloud.");return{slug:slot.slug,versionId:slot.latestVersionId}}path(){return this.storePath??localCommentsPath(folderIdentity(this.ctxFor()))}async read(){let folderId=folderIdentity(this.ctxFor());try{let parsed=LocalCommentsFileSchema.parse(JSON.parse(await readFile9(this.path(),"utf8")));if(parsed.folderId===folderId)return parsed}catch(error){if(error.code!=="ENOENT"&&!(error instanceof SyntaxError))throw error}return{version:1,folderId,threads:[]}}async write(file){await writeJsonAtomic(this.path(),LocalCommentsFileSchema.parse(file))}serialized(fn){let run=this.tail.then(fn,fn);return this.tail=run.then(()=>{return},()=>{return}),run}validateBoardAndAnchor(boardId,anchor){let folder=this.ctxFor().folder,board=folder.boards.get(boardId);if(!board)throw new CommentStoreError("invalid",`No board named ${boardId}.`);if(!anchor)return;let parsed=CommentAnchorSchema.parse(anchor);if(parsed.boardId!==boardId)throw new CommentStoreError("invalid","The anchor belongs to a different board.");if(parsed.kind==="board")return;let frame=board.frames.find((candidate)=>candidate.id===parsed.frameId);if(!frame||frame.screen!==parsed.screenId)throw new CommentStoreError("invalid","The anchor frame is not on this board or screen.");if(!folder.screens.has(parsed.screenId))throw new CommentStoreError("invalid",`No screen named ${parsed.screenId}.`)}view(thread){let anchor=thread.anchor;if(!anchor||anchor.kind==="board")return{...thread,anchorState:{status:"board"}};let folder=this.ctxFor().folder,board=folder.boards.get(thread.boardId),frame=board?.frames.find((candidate)=>candidate.id===anchor.frameId),screen=folder.screens.get(anchor.screenId);if(!board||!frame||frame.screen!==anchor.screenId||!screen)return{...thread,anchorState:{status:"stale"}};let resolvedPath=resolveLocator(screen.tree,anchor.locator);return resolvedPath?{...thread,anchorState:{status:"attached",resolvedPath}}:{...thread,anchorState:{status:"stale"}}}async sharedThreads(){return this.shared?(await this.shared.refresh()).threads:[]}async refreshShared(){let result=await this.shared?.refresh();if(result?.changed)for(let boardId of result.boardIds)this.ctxFor().broadcast({type:"comments-changed",boardId,scope:"shared"});return result}async list(boardId,status="open",scope="all"){if(!this.ctxFor().folder.boards.has(boardId))throw new CommentStoreError("invalid",`No board named ${boardId}.`);let[file,shared]=await Promise.all([this.read(),this.sharedThreads()]);return[...file.threads,...shared].filter((thread)=>thread.boardId===boardId&&(status==="all"||thread.status===status)&&(scope==="all"||thread.scope===scope)).sort((a,b)=>b.updatedAt.localeCompare(a.updatedAt)).map((thread)=>this.view(thread))}async listAll(status="open",scope="all"){let[file,shared]=await Promise.all([this.read(),this.sharedThreads()]);return[...file.threads,...shared].filter((thread)=>(status==="all"||thread.status===status)&&(scope==="all"||thread.scope===scope)).sort((a,b)=>b.updatedAt.localeCompare(a.updatedAt)).map((thread)=>this.view(thread))}countOpenSync(){let shared=this.shared?.cachedSync().filter((thread)=>thread.status==="open").length??0;try{let parsed=LocalCommentsFileSchema.parse(JSON.parse(readFileSync4(this.path(),"utf8")));return(parsed.folderId===folderIdentity(this.ctxFor())?parsed.threads.filter((thread)=>thread.status==="open").length:0)+shared}catch{return shared}}async get(id){let thread=(await this.read()).threads.find((candidate)=>candidate.id===id);if(thread)return this.view(thread);let shared=await this.shared?.get(id);if(shared)return this.view(shared);throw new CommentStoreError("not-found","No such comment thread.")}create(input){return this.serialized(async()=>{this.validateBoardAndAnchor(input.boardId,input.anchor);let body=input.body.trim();if(!body)throw new CommentStoreError("invalid","Comment body is required.");let author=CommentAuthorSchema.parse(input.author??defaultAuthor());if(input.scope==="shared")return this.view(await this.createShared(input.boardId,body,input.anchor,ownerAuthorKind(author)));let now=new Date().toISOString(),file=await this.read(),thread=CommentThreadSchema.parse({id:crypto.randomUUID(),scope:"local",folderId:file.folderId,boardId:input.boardId,...input.anchor?{anchor:input.anchor}:{},origin:{kind:"local"},status:"open",messages:[message(body,author,now)],createdAt:now,updatedAt:now});return file.threads.push(thread),await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:thread.boardId,scope:"local"}),this.view(thread)})}reply(id,input){return this.serialized(async()=>{let body=input.body.trim();if(!body)throw new CommentStoreError("invalid","Reply body is required.");let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.reply(id,body,ownerAuthorKind(input.author));return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],now=new Date().toISOString(),next=CommentThreadSchema.parse({...current,messages:[...current.messages,message(body,input.author??defaultAuthor(),now)],updatedAt:now});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}setResolved(id,resolved){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.setResolved(id,resolved);return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],now=new Date().toISOString(),next=CommentThreadSchema.parse({...current,status:resolved?"resolved":"open",updatedAt:now,...resolved?{resolvedAt:now}:{resolvedAt:void 0}});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}async createShared(boardId,body,anchor,authorKind){let shared=this.shared;if(!shared)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let target=await this.cloudTarget(boardId),thread=await shared.create(target.slug,{versionId:target.versionId,boardId,...anchor?{anchor}:{},body,authorKind}).catch((error)=>{throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))});return this.ctxFor().broadcast({type:"comments-changed",boardId,scope:"shared"}),thread}promoteToShared(id){return this.serialized(async()=>{let shared=this.shared;if(!shared)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(await this.shared?.get(id))throw new CommentStoreError("invalid","This thread is already a cloud thread.");throw new CommentStoreError("not-found","No such comment thread.")}let local=file.threads[index],[first,...rest]=local.messages;if(!first)throw new CommentStoreError("invalid","That thread has no messages to move.");let promoted=await this.createShared(local.boardId,first.body,local.anchor,ownerAuthorKind(first.author));try{for(let message of rest)promoted=await shared.reply(promoted.id,message.body,ownerAuthorKind(message.author));if(local.status==="resolved")promoted=await shared.setResolved(promoted.id,!0)}catch(error){throw new CommentStoreError("invalid",`The thread moved to the cloud but its replies did not: ${error instanceof Error?error.message:String(error)}`)}let latest=await this.read();return latest.threads=latest.threads.filter((candidate)=>candidate.id!==id),await this.write(latest),this.ctxFor().broadcast({type:"comments-changed",boardId:local.boardId,scope:"local"}),this.view(promoted)})}deleteMessage(id,messageId){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.deleteMessage(id,messageId);return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],remaining=current.messages.filter((candidate)=>candidate.id!==messageId);if(remaining.length===current.messages.length)throw new CommentStoreError("not-found","No such comment.");if(remaining.length===0)throw new CommentStoreError("invalid","That is the thread's only comment \u2014 delete the thread instead.");let next=CommentThreadSchema.parse({...current,messages:remaining,updatedAt:new Date().toISOString()});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}delete(id){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(await this.shared?.get(id))throw new CommentStoreError("invalid","Shared comment threads cannot be deleted. Resolve the conversation instead.");throw new CommentStoreError("not-found","No such comment thread.")}let[removed]=file.threads.splice(index,1);if(!removed)throw new CommentStoreError("not-found","No such comment thread.");return await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:removed.boardId,scope:"local"}),{removedId:id,boardId:removed.boardId}})}}import{readFile as readFile10,unlink}from"fs/promises";import{join as join17}from"path";var assetsFilePath=(root)=>join17(root,"assets.json");async function readAssetsFile(root){let raw;try{raw=await readFile10(assetsFilePath(root),"utf8")}catch{return EMPTY_ASSETS_FILE}try{let parsed=AssetsFileSchema.safeParse(JSON.parse(raw));return parsed.success?parsed.data:EMPTY_ASSETS_FILE}catch{return EMPTY_ASSETS_FILE}}var writeChains=new Map;function serialize(root,op){let next=(writeChains.get(root)??Promise.resolve()).then(op,op);return writeChains.set(root,next.catch(()=>{})),next}function assetReferences(folder,assetPath){let url=`/${assetPath}`,hits=[],uses=(value)=>{let json=JSON.stringify(value)??"";return json.includes(`"${url}"`)||json.includes(`"${assetPath}"`)};for(let[id,screen]of folder.screens)if(uses(screen))hits.push(`screen "${id}"`);for(let[id,snippet]of folder.snippets)if(uses(snippet))hits.push(`snippet "${id}"`);return hits}async function deleteGeneratedAsset(folder,assetPath){let root=folder.root;if(!assetPath.startsWith("assets/")||assetPath.includes(".."))return{ok:!1,reason:`"${assetPath}" is not a path inside this folder's asset store.`};let used=assetReferences(folder,assetPath);if(used.length>0)return{ok:!1,reason:`${assetPath} is still used by ${used.join(", ")} \u2014 point those at another image first.`};return await unlink(join17(root,assetPath)).catch(()=>{}),await serialize(root,async()=>{let file=await readAssetsFile(root);if(!(assetPath in file.generated))return;let{[assetPath]:_dropped,...rest}=file.generated;await writeJsonAtomic(assetsFilePath(root),{...file,generated:rest})}),{ok:!0}}async function recordGeneratedAssets(root,entries){if(Object.keys(entries).length===0)return;await serialize(root,async()=>{let file=await readAssetsFile(root);await writeJsonAtomic(assetsFilePath(root),{...file,generated:{...file.generated,...entries}})})}import{readFile as readFile11}from"fs/promises";import{isAbsolute as isAbsolute2,join as join18,normalize as normalize2,relative as relative2,resolve as resolve7}from"path";var INTENTS=["photo","illustration","graphic","texture","icon","vector","mark","edit","cutout","upscale"],ASPECTS=["1:1","4:3","3:4","16:9","9:16","3:2","2:3"],FETCH_TIMEOUT_MS2=120000,MAX_REFERENCE_BYTES=4194304,MAX_REFERENCES=3,REFERENCE_MIME={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif"},NOT_AVAILABLE="Hosted generation isn't available on this velloo-cloud server.",HINTS={400:"Fix the arguments and retry \u2014 nothing was generated or charged.",402:"Ask the user to top up credits before retrying.",404:NOT_AVAILABLE,429:"The limit is per-account per minute \u2014 wait a minute, then retry.",502:"Retry once.",503:NOT_AVAILABLE};function formatDollars(micros){let value=micros/1e6,fixed=value.toFixed(2);return Number(fixed)===value?`$${fixed}`:`$${value}`}function describeGenerateFailure(error){if(error.kind==="Unreachable")return`${describeCloudError(error)}. Nothing was generated or charged.`;if(error.kind!=="HttpFailure")return describeCloudError(error);let hint=HINTS[error.status],detail=error.detail==="unknown"?`velloo-cloud rejected the generation request (${error.status}).`:error.detail.trimEnd(),sentence=/[.!?]$/.test(detail)?detail:`${detail}.`;return hint?`${sentence} ${hint}`:sentence}var DATA_URL=/^data:image\/(png|svg\+xml);base64,([A-Za-z0-9+/=\s]+)$/;async function encodeReference(root,ref){let cleaned=ref.replace(/^\/+/,"");if(isAbsolute2(cleaned)||normalize2(cleaned).startsWith(".."))return err(invalidRequest(`Reference "${ref}" is outside the design folder \u2014 pass a folder-relative path like "assets/hero.png".`));let full=resolve7(join18(root,cleaned)),rel=relative2(resolve7(root),full);if(rel.startsWith("..")||isAbsolute2(rel))return err(invalidRequest(`Reference "${ref}" is outside the design folder \u2014 pass a folder-relative path like "assets/hero.png".`));let ext=(full.split(".").pop()??"").toLowerCase(),mime=REFERENCE_MIME[ext];if(!mime)return err(invalidRequest(`Reference "${ref}" isn't a supported image (expected ${Object.keys(REFERENCE_MIME).join(", ")}).`));let bytes;try{bytes=await readFile11(full)}catch{return err(invalidRequest(`Reference "${ref}" doesn't exist in this design folder.`))}if(bytes.length>MAX_REFERENCE_BYTES)return err(invalidRequest(`Reference "${ref}" is ${(bytes.length/1048576).toFixed(1)} MB \u2014 the limit is ${MAX_REFERENCE_BYTES/1048576} MB. Use a smaller source image.`));return ok(`data:${mime};base64,${bytes.toString("base64")}`)}async function fetchIntentCatalog(cloud){let token=await currentToken(cloud);if(!token)return err(loggedOut("Not signed in to velloo-cloud"));if(!isSecureCloudUrl(cloud.url))return err(insecureCloudUrl(cloud.url));return cloudFetch(`${cloud.url}/v1/assets/intents`,IntentCatalogResponseSchema,{operation:"reading the generation catalogue",token,timeoutMs:1e4})}async function generateAsset(root,cloud,req){let token=await currentToken(cloud);if(!token)return err(loggedOut("Not signed in to velloo-cloud"));if(!isSecureCloudUrl(cloud.url))return err(insecureCloudUrl(cloud.url));let refPaths=req.reference??[];if(refPaths.length>MAX_REFERENCES)return err(invalidRequest(`At most ${MAX_REFERENCES} reference images per generation (got ${refPaths.length}).`));let references=[];for(let ref of refPaths){let encoded=await encodeReference(root,ref);if(!encoded.ok)return encoded;references.push(encoded.value)}let res;try{res=await fetch(`${cloud.url}/v1/assets/generate`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${token}`},body:JSON.stringify({prompt:req.prompt,intent:req.intent,...req.aspect?{aspect:req.aspect}:{},...req.count&&req.count>1?{count:req.count}:{},...references.length>0?{reference:references}:{}}),signal:AbortSignal.timeout(FETCH_TIMEOUT_MS2)})}catch(e){return err(unreachable(e,{url:cloud.url}))}if(!res.ok){let{detail,code}=await readFailure(res);return err(cloudFailure("generation",res.status,detail,code))}let parsed=await cloudJson(res,GenerateResponseSchema,"generation");if(!parsed.ok)return parsed;let payload=parsed.value,stem=req.filename?.replace(/\.(png|svg)$/i,"")??payload.id,files=[];for(let[i,asset]of payload.assets.entries()){let match=DATA_URL.exec(asset.dataUrl);if(!match)return err(protocolViolation("velloo-cloud returned a data URL this server doesn't understand (expected base64 png or svg)"));let ext=match[1]==="png"?"png":"svg",bytes=Buffer.from(match[2],"base64");if(ext==="svg"&&svgLooksActive(bytes.toString("utf8")))return err(protocolViolation("velloo-cloud returned SVG containing active content (script/event handlers) \u2014 refusing to store it; retry the generation"));let name=payload.assets.length===1?stem:`${stem}-${i+1}`,stored=await storeAsset(root,`${name}.${ext}`,bytes);files.push({...stored,kind:asset.kind,...ext==="png"?pngSize2(bytes)??{}:{},...ext==="svg"?{content:bytes.toString("utf8")}:{}})}await recordGeneratedAssets(root,Object.fromEntries(files.map((f)=>[f.assetPath,{prompt:req.prompt,intent:req.intent,...req.aspect?{aspect:req.aspect}:{},...f.width&&f.height?{width:f.width,height:f.height}:{},generatedAt:new Date().toISOString(),...refPaths.length>0?{reference:refPaths}:{},...req.replaces?{replaces:req.replaces}:{}}]))).catch(()=>{});let first=files[0];return ok({assets:files,intent:req.intent,chargedMicros:payload.chargedMicros,balanceMicros:payload.balanceMicros,cost:`cost ${formatDollars(payload.chargedMicros)}, balance ${formatDollars(payload.balanceMicros)}`,assetPath:first.assetPath,url:first.url,kind:first.kind,...first.width&&first.height?{width:first.width,height:first.height}:{},...first.content?{content:first.content}:{}})}var GenerateBody=object({prompt:string().min(1).max(2000),intent:_enum(INTENTS),aspect:_enum(ASPECTS).optional(),reference:array(string()).max(3).optional(),replaces:string().max(512).optional()});function createAssetsRouter(folder,cloud){let app=new Hono2;app.get("/",async(c)=>{let file=await readAssetsFile(folder().root);return c.json({generated:file.generated})});let fail=(c,status,kind,message)=>c.json({error:{kind,message}},status);return app.get("/intents",async(c)=>{if(!cloud)return c.json({intents:[]});let r=await fetchIntentCatalog(cloud);return c.json(r.ok?r.value:{intents:[]})}),app.post("/delete",async(c)=>{let body=await c.req.json().catch(()=>{return});if(typeof body?.assetPath!=="string")return fail(c,400,"BadRequest","assetPath is required.");let r=await deleteGeneratedAsset(folder(),body.assetPath);if(!r.ok)return fail(c,409,"AssetInUse",r.reason);return c.json({deleted:body.assetPath})}),app.post("/generate",async(c)=>{if(!cloud)return fail(c,503,"LoggedOut","This daemon has no velloo-cloud connection \u2014 run `velloo login`, then reopen the canvas.");let parsed=GenerateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return fail(c,400,"BadRequest",parsed.error.issues[0]?.message??"invalid request");let r=await generateAsset(folder().root,cloud,parsed.data);if(!r.ok){let status=r.error.kind==="LoggedOut"?401:r.error.kind==="InvalidRequest"?400:r.error.kind==="HttpFailure"?r.error.status:502;return fail(c,status,r.error.kind,describeGenerateFailure(r.error))}return c.json(r.value)}),app}function createAuthRouter(auth){let app=new Hono2;return app.get("/status",async(c)=>{if(!auth)return c.json(LOGGED_OUT);return c.json(await auth.status())}),app.post("/login",async(c)=>{if(!auth)return c.json({error:"this server has no credential store to sign in to"},503);return c.json(await auth.beginLogin())}),app.post("/login/cancel",async(c)=>{if(auth)await auth.cancelLogin();return c.json({ok:!0})}),app.post("/logout",async(c)=>{if(auth)await auth.logout();return c.json({ok:!0})}),app}function createLiveRouter(bundler){let r=new Hono2,headers={"Content-Type":"text/javascript; charset=utf-8","Cache-Control":"no-store","Access-Control-Allow-Origin":"*"};return r.get("/bundle.js",async(c)=>{let{code,errors}=await bundler.build(),body=errors.length?`${code}
|
|
153
|
+
${value}`,dataLines++;break;case"id":value.includes("\x00")||(id=value);break;case"retry":/^\d+$/.test(value)?onRetry(parseInt(value,10)):onError(new ParseError(`Invalid \`retry\` value: "${value}"`,{type:"invalid-retry",value,line}));break;default:onError(new ParseError(`Unknown field "${field.length>20?`${field.slice(0,20)}\u2026`:field}"`,{type:"unknown-field",field,value,line}));break}}function dispatchEvent(){dataLines>0&&onEvent({id,event:eventType,data}),id=void 0,data="",dataLines=0,eventType=void 0}function reset(options={}){if(options.consume&&pendingFragments.length>0){let incompleteLine=pendingFragments.join("");parseLine(incompleteLine,0,incompleteLine.length)}isFirstChunk=!0,id=void 0,data="",dataLines=0,eventType=void 0,pendingFragments.length=0,pendingFragmentsLength=0,terminated=!1}return{feed,reset}}function isDataPrefix(chunk,i,firstCharCode){return firstCharCode===100&&chunk.charCodeAt(i+1)===97&&chunk.charCodeAt(i+2)===116&&chunk.charCodeAt(i+3)===97&&chunk.charCodeAt(i+4)===58}function isEventPrefix(chunk,i,firstCharCode){return firstCharCode===101&&chunk.charCodeAt(i+1)===118&&chunk.charCodeAt(i+2)===101&&chunk.charCodeAt(i+3)===110&&chunk.charCodeAt(i+4)===116&&chunk.charCodeAt(i+5)===58}class EventSourceParserStream extends TransformStream{constructor({onError,onRetry,onComment,maxBufferSize}={}){let parser;super({start(controller){parser=createParser({onEvent:(event)=>{controller.enqueue(event)},onError(error){typeof onError=="function"&&onError(error),(onError==="terminate"||error.type==="max-buffer-size-exceeded")&&controller.error(error)},onRetry,onComment,maxBufferSize})},transform(chunk){parser.feed(chunk)}})}}var DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS={initialReconnectionDelay:1000,maxReconnectionDelay:30000,reconnectionDelayGrowFactor:1.5,maxRetries:2};class StreamableHTTPError extends Error{constructor(code,message){super(`Streamable HTTP error: ${message}`);this.code=code}}class StreamableHTTPClientTransport{constructor(url,opts){this._hasCompletedAuthFlow=!1,this._url=url,this._resourceMetadataUrl=void 0,this._scope=void 0,this._requestInit=opts?.requestInit,this._authProvider=opts?.authProvider,this._fetch=opts?.fetch,this._fetchWithInit=createFetchWithInit(opts?.fetch,opts?.requestInit),this._sessionId=opts?.sessionId,this._reconnectionOptions=opts?.reconnectionOptions??DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS}async _authThenStart(){if(!this._authProvider)throw new UnauthorizedError("No auth provider");let result;try{result=await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})}catch(error){throw this.onerror?.(error),error}if(result!=="AUTHORIZED")throw new UnauthorizedError;return await this._startOrAuthSse({resumptionToken:void 0})}async _commonHeaders(){let headers={};if(this._authProvider){let tokens=await this._authProvider.tokens();if(tokens)headers.Authorization=`Bearer ${tokens.access_token}`}if(this._sessionId)headers["mcp-session-id"]=this._sessionId;if(this._protocolVersion)headers["mcp-protocol-version"]=this._protocolVersion;let extraHeaders=normalizeHeaders(this._requestInit?.headers);return new Headers({...headers,...extraHeaders})}async _startOrAuthSse(options){let{resumptionToken}=options;try{let headers=await this._commonHeaders();if(headers.set("Accept","text/event-stream"),resumptionToken)headers.set("last-event-id",resumptionToken);let response=await(this._fetch??fetch)(this._url,{method:"GET",headers,signal:this._abortController?.signal});if(!response.ok){if(await response.body?.cancel(),response.status===401&&this._authProvider)return await this._authThenStart();if(response.status===405)return;throw new StreamableHTTPError(response.status,`Failed to open SSE stream: ${response.statusText}`)}this._handleSseStream(response.body,options,!0)}catch(error){throw this.onerror?.(error),error}}_getNextReconnectionDelay(attempt){if(this._serverRetryMs!==void 0)return this._serverRetryMs;let initialDelay=this._reconnectionOptions.initialReconnectionDelay,growFactor=this._reconnectionOptions.reconnectionDelayGrowFactor,maxDelay=this._reconnectionOptions.maxReconnectionDelay;return Math.min(initialDelay*Math.pow(growFactor,attempt),maxDelay)}_scheduleReconnection(options,attemptCount=0){let maxRetries=this._reconnectionOptions.maxRetries;if(attemptCount>=maxRetries){this.onerror?.(Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));return}let delay=this._getNextReconnectionDelay(attemptCount);this._reconnectionTimeout=setTimeout(()=>{this._startOrAuthSse(options).catch((error)=>{this.onerror?.(Error(`Failed to reconnect SSE stream: ${error instanceof Error?error.message:String(error)}`)),this._scheduleReconnection(options,attemptCount+1)})},delay)}_handleSseStream(stream,options,isReconnectable){if(!stream)return;let{onresumptiontoken,replayMessageId}=options,lastEventId,hasPrimingEvent=!1,receivedResponse=!1;(async()=>{try{let reader=stream.pipeThrough(new TextDecoderStream).pipeThrough(new EventSourceParserStream({onRetry:(retryMs)=>{this._serverRetryMs=retryMs}})).getReader();while(!0){let{value:event,done}=await reader.read();if(done)break;if(event.id)lastEventId=event.id,hasPrimingEvent=!0,onresumptiontoken?.(event.id);if(!event.data)continue;if(!event.event||event.event==="message")try{let message=JSONRPCMessageSchema.parse(JSON.parse(event.data));if(isJSONRPCResultResponse(message)){if(receivedResponse=!0,replayMessageId!==void 0)message.id=replayMessageId}this.onmessage?.(message)}catch(error){this.onerror?.(error)}}if((isReconnectable||hasPrimingEvent)&&!receivedResponse&&this._abortController&&!this._abortController.signal.aborted)this._scheduleReconnection({resumptionToken:lastEventId,onresumptiontoken,replayMessageId},0)}catch(error){if(this.onerror?.(Error(`SSE stream disconnected: ${error}`)),(isReconnectable||hasPrimingEvent)&&!receivedResponse&&this._abortController&&!this._abortController.signal.aborted)try{this._scheduleReconnection({resumptionToken:lastEventId,onresumptiontoken,replayMessageId},0)}catch(error){this.onerror?.(Error(`Failed to reconnect: ${error instanceof Error?error.message:String(error)}`))}}})()}async start(){if(this._abortController)throw Error("StreamableHTTPClientTransport already started! If using Client class, note that connect() calls start() automatically.");this._abortController=new AbortController}async finishAuth(authorizationCode){if(!this._authProvider)throw new UnauthorizedError("No auth provider");if(await auth(this._authProvider,{serverUrl:this._url,authorizationCode,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new UnauthorizedError("Failed to authorize")}async close(){if(this._reconnectionTimeout)clearTimeout(this._reconnectionTimeout),this._reconnectionTimeout=void 0;this._abortController?.abort(),this.onclose?.()}async send(message,options){try{let{resumptionToken,onresumptiontoken}=options||{};if(resumptionToken){this._startOrAuthSse({resumptionToken,replayMessageId:isJSONRPCRequest(message)?message.id:void 0}).catch((err)=>this.onerror?.(err));return}let headers=await this._commonHeaders();headers.set("content-type","application/json"),headers.set("accept","application/json, text/event-stream");let init={...this._requestInit,method:"POST",headers,body:JSON.stringify(message),signal:this._abortController?.signal},response=await(this._fetch??fetch)(this._url,init),sessionId=response.headers.get("mcp-session-id");if(sessionId)this._sessionId=sessionId;if(!response.ok){let text=await response.text().catch(()=>null);if(response.status===401&&this._authProvider){if(this._hasCompletedAuthFlow)throw new StreamableHTTPError(401,"Server returned 401 after successful authentication");let{resourceMetadataUrl,scope}=extractWWWAuthenticateParams(response);if(this._resourceMetadataUrl=resourceMetadataUrl,this._scope=scope,await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetchWithInit})!=="AUTHORIZED")throw new UnauthorizedError;return this._hasCompletedAuthFlow=!0,this.send(message)}if(response.status===403&&this._authProvider){let{resourceMetadataUrl,scope,error}=extractWWWAuthenticateParams(response);if(error==="insufficient_scope"){let wwwAuthHeader=response.headers.get("WWW-Authenticate");if(this._lastUpscopingHeader===wwwAuthHeader)throw new StreamableHTTPError(403,"Server returned 403 after trying upscoping");if(scope)this._scope=scope;if(resourceMetadataUrl)this._resourceMetadataUrl=resourceMetadataUrl;if(this._lastUpscopingHeader=wwwAuthHeader??void 0,await auth(this._authProvider,{serverUrl:this._url,resourceMetadataUrl:this._resourceMetadataUrl,scope:this._scope,fetchFn:this._fetch})!=="AUTHORIZED")throw new UnauthorizedError;return this.send(message)}}throw new StreamableHTTPError(response.status,`Error POSTing to endpoint: ${text}`)}if(this._hasCompletedAuthFlow=!1,this._lastUpscopingHeader=void 0,response.status===202){if(await response.body?.cancel(),isInitializedNotification(message))this._startOrAuthSse({resumptionToken:void 0}).catch((err)=>this.onerror?.(err));return}let hasRequests=(Array.isArray(message)?message:[message]).filter((msg)=>("method"in msg)&&("id"in msg)&&msg.id!==void 0).length>0,contentType=response.headers.get("content-type"),responseMediaType=mediaTypeEssence(contentType);if(hasRequests)if(responseMediaType==="text/event-stream")this._handleSseStream(response.body,{onresumptiontoken},!1);else if(responseMediaType==="application/json"){let data=await response.json(),responseMessages=Array.isArray(data)?data.map((msg)=>JSONRPCMessageSchema.parse(msg)):[JSONRPCMessageSchema.parse(data)];for(let msg of responseMessages)this.onmessage?.(msg)}else throw await response.body?.cancel(),new StreamableHTTPError(-1,`Unexpected content type: ${contentType}`);else await response.body?.cancel()}catch(error){throw this.onerror?.(error),error}}get sessionId(){return this._sessionId}async terminateSession(){if(!this._sessionId)return;try{let headers=await this._commonHeaders(),init={...this._requestInit,method:"DELETE",headers,signal:this._abortController?.signal},response=await(this._fetch??fetch)(this._url,init);if(await response.body?.cancel(),!response.ok&&response.status!==405)throw new StreamableHTTPError(response.status,`Failed to terminate session: ${response.statusText}`);this._sessionId=void 0}catch(error){throw this.onerror?.(error),error}}setProtocolVersion(version){this._protocolVersion=version}get protocolVersion(){return this._protocolVersion}async resumeStream(lastEventId,options){await this._startOrAuthSse({resumptionToken:lastEventId,onresumptiontoken:options?.onresumptiontoken})}}async function runStdioMcpProxy(httpMcpUrl,opts={}){let stdio=opts.agentTransport??new StdioServerTransport,closing=!1,initRequest=null,reinitIds=new Set,reinitSeq=0,reinitResults=new Map,reconnecting=null,switching=null,held=[],wire=(transport)=>(transport.onmessage=(msg)=>{if(isJSONRPCResponse(msg)&&typeof msg.id==="string"&&reinitIds.has(msg.id)){reinitIds.delete(msg.id),reinitResults.set(msg.id,msg.result);return}let directive=isJSONRPCResponse(msg)?switchDirective(msg):null;if(directive&&isJSONRPCResponse(msg)){performSwitch(msg,directive);return}stdio.send(msg).catch(()=>{return})},transport.onclose=()=>{if(transport===http)close()},transport.onerror=(err)=>console.error("velloo mcp: canvas daemon connection error:",err),transport),http=wire(new StreamableHTTPClientTransport(new URL(httpMcpUrl))),close=async()=>{if(closing)return;closing=!0,await stdio.close().catch(()=>{return}),await http.close().catch(()=>{return}),opts.onExit?.()},bind=async(url,replayInit)=>{let next=wire(new StreamableHTTPClientTransport(new URL(url))),initResult=null;try{if(await next.start(),replayInit&&initRequest){let reinitId=`velloo-reinit-${++reinitSeq}`;reinitIds.add(reinitId),await next.send({...initRequest,id:reinitId}),initResult=reinitResults.get(reinitId)??null,reinitResults.delete(reinitId),await next.send({jsonrpc:"2.0",method:"notifications/initialized"})}}catch(err){return console.error("velloo mcp: connecting to the canvas daemon failed:",err),await next.close().catch(()=>{return}),!1}let prev=http;return http=next,await prev.terminateSession().catch(()=>{return}),await prev.close().catch(()=>{return}),initResult},reconnect=async(replayInit)=>{if(!opts.rediscover)return!1;let url=await opts.rediscover().catch(()=>null);if(!url||closing)return!1;if(await bind(url,replayInit)===!1)return!1;return console.error(`velloo mcp: reconnected to the canvas daemon at ${url}`),!0},performSwitch=(response,directive)=>{switching=(async()=>{let reply=(result)=>stdio.send({jsonrpc:"2.0",id:response.id,result}).catch(()=>{return}),failed=(message)=>reply({isError:!0,content:[{type:"text",text:JSON.stringify({kind:"DesignSwitchFailed",name:directive.name,message})}]});if(!opts.switchTo){await failed("this connection can't move to another design");return}let target=await opts.switchTo(directive.root).catch((err)=>({error:err instanceof Error?err.message:String(err)}));if("error"in target){await failed(target.error);return}let init=await bind(target.url,!0);if(init===!1){await failed(`the canvas daemon for "${directive.name}" did not accept the session`);return}let instructions=init?.instructions;await reply({content:[{type:"text",text:JSON.stringify({kind:"DesignSwitched",name:directive.name})},...typeof instructions==="string"?[{type:"text",text:instructions}]:[]]}),await stdio.send({jsonrpc:"2.0",method:"notifications/tools/list_changed"}).catch(()=>{return}),console.error(`velloo mcp: switched to design "${directive.name}" at ${target.url}`)})().finally(()=>{switching=null;for(let msg of held.splice(0))forward(msg)})},reconnectOnce=(replayInit)=>(reconnecting??=reconnect(replayInit).finally(()=>{reconnecting=null}),reconnecting),forward=async(msg)=>{let error;try{await http.send(msg);return}catch(err){error=err}console.error("velloo mcp: forwarding to canvas daemon failed:",error);let isInit=isJSONRPCRequest(msg)&&msg.method==="initialize";if(await reconnectOnce(!isInit))try{await http.send(msg);return}catch(err){error=err}if(isJSONRPCRequest(msg)){let detail=error instanceof Error?error.message:String(error),reply={jsonrpc:"2.0",id:msg.id,error:{code:-32000,message:opts.rediscover?`velloo: the canvas daemon is unreachable and could not be respawned (${detail}) \u2014 check .design/cache/daemon.log, then retry`:`velloo: forwarding to the canvas daemon failed (${detail}) \u2014 retry`}};stdio.send(reply).catch(()=>{return})}};return stdio.onmessage=(msg)=>{if(isJSONRPCRequest(msg)&&msg.method==="initialize")initRequest=msg;if(switching){held.push(msg);return}forward(msg)},stdio.onclose=()=>void close(),await http.start(),await stdio.start(),{close}}function switchDirective(response){let directive=response.result?._meta?.[SWITCH_DESIGN_META];return typeof directive?.name==="string"&&typeof directive.root==="string"?{name:directive.name,root:directive.root}:null}import{existsSync as existsSync11}from"fs";import{extname as extname3,join as join38,sep as sep4}from"path";var compose=(middleware,onError,onNotFound)=>(context,next)=>{let index=-1;return dispatch(0);async function dispatch(i){if(i<=index)throw Error("next() called multiple times");index=i;let res,isError=!1,handler;if(middleware[i])handler=middleware[i][0][0],context.req.routeIndex=i;else handler=i===middleware.length&&next||void 0;if(handler)try{res=await handler(context,()=>dispatch(i+1))}catch(err){if(err instanceof Error&&onError)context.error=err,res=await onError(err,context),isError=!0;else throw err}else if(context.finalized===!1&&onNotFound)res=await onNotFound(context);if(res&&(context.finalized===!1||isError))context.res=res;return context}};var GET_MATCH_RESULT=Symbol();var bufferToFormData=(arrayBuffer,contentType)=>new Response(arrayBuffer,{headers:{"Content-Type":contentType.replace(/^[^;]+/,(mediaType)=>mediaType.toLowerCase())}}).formData();var MAX_NESTING_DEPTH=32,MAX_NESTED_OBJECTS=1e4,isRawRequest=(request)=>("headers"in request),parseBody=async(request,options=Object.create(null))=>{let{all=!1,dot=!1}=options,mediaType=(isRawRequest(request)?request.headers:request.raw.headers).get("Content-Type")?.split(";")[0].trim().toLowerCase();if(mediaType==="multipart/form-data"||mediaType==="application/x-www-form-urlencoded")return parseFormData(request,{all,dot});return{}};async function parseFormData(request,options){if(!isRawRequest(request)&&request.bodyCache.formData)return convertFormDataToBodyData(await request.bodyCache.formData,options);let headers=isRawRequest(request)?request.headers:request.raw.headers,arrayBuffer=await request.arrayBuffer(),formDataPromise=bufferToFormData(arrayBuffer,headers.get("Content-Type")||"");if(!isRawRequest(request))request.bodyCache.formData=formDataPromise;let formData=await formDataPromise;if(formData)return convertFormDataToBodyData(formData,options);return{}}function convertFormDataToBodyData(formData,options){let form=Object.create(null),nestingState={count:0};if(formData.forEach((value,key)=>{if(!(options.all||key.endsWith("[]")))form[key]=value;else handleParsingAllValues(form,key,value)}),options.dot)Object.entries(form).forEach(([key,value])=>{if(key.includes("."))handleParsingNestedValues(form,key,value,nestingState),delete form[key]});return form}var handleParsingAllValues=(form,key,value)=>{if(form[key]!==void 0)if(Array.isArray(form[key]))form[key].push(value);else form[key]=[form[key],value];else if(!key.endsWith("[]"))form[key]=value;else form[key]=[value]},handleParsingNestedValues=(form,key,value,state)=>{if(/(?:^|\.)__proto__\./.test(key))return;let nestedForm=form,keys=key.split(".",MAX_NESTING_DEPTH+2);if(keys.length>MAX_NESTING_DEPTH+1)throwNestingLimitExceeded();keys.forEach((key2,index)=>{if(index===keys.length-1)nestedForm[key2]=value;else{if(!nestedForm[key2]||typeof nestedForm[key2]!=="object"||Array.isArray(nestedForm[key2])||nestedForm[key2]instanceof File){if(state.count++>=MAX_NESTED_OBJECTS)throwNestingLimitExceeded();nestedForm[key2]=Object.create(null)}nestedForm=nestedForm[key2]}})},throwNestingLimitExceeded=()=>{throw Error("Nesting limit exceeded")};var splitPath=(path)=>{let paths=path.split("/");if(paths[0]==="")paths.shift();return paths},splitRoutingPath=(routePath)=>{let{groups,path}=extractGroupsFromPath(routePath),paths=splitPath(path);return replaceGroupMarks(paths,groups)},extractGroupsFromPath=(path)=>{let groups=[];return path=path.replace(/\{[^}]+\}/g,(match,index)=>{let mark=`@${index}`;return groups.push([mark,match]),mark}),{groups,path}},replaceGroupMarks=(paths,groups)=>{for(let i=groups.length-1;i>=0;i--){let[mark]=groups[i];for(let j=paths.length-1;j>=0;j--)if(paths[j].includes(mark)){paths[j]=paths[j].replace(mark,groups[i][1]);break}}return paths},patternCache={},getPattern=(label,next)=>{if(label==="*")return"*";let match=label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);if(match){let cacheKey=`${label}#${next}`;if(!patternCache[cacheKey])if(match[2])patternCache[cacheKey]=next&&next[0]!==":"&&next[0]!=="*"?[cacheKey,match[1],new RegExp(`^${match[2]}(?=/${next})`)]:[label,match[1],new RegExp(`^${match[2]}$`)];else patternCache[cacheKey]=[label,match[1],!0];return patternCache[cacheKey]}return null},tryDecode=(str,decoder)=>{try{return decoder(str)}catch{return str.replace(/(?:%[0-9A-Fa-f]{2})+/g,(match)=>{try{return decoder(match)}catch{return match}})}},tryDecodeURI=(str)=>tryDecode(str,decodeURI),getPath=(request)=>{let url=request.url,start=url.indexOf("/",url.indexOf(":")+4),i=start;for(;i<url.length;i++){let charCode=url.charCodeAt(i);if(charCode===37){let queryIndex=url.indexOf("?",i),hashIndex=url.indexOf("#",i),end=queryIndex===-1?hashIndex===-1?void 0:hashIndex:hashIndex===-1?queryIndex:Math.min(queryIndex,hashIndex),path=url.slice(start,end);return tryDecodeURI(path.includes("%25")?path.replace(/%25/g,"%2525"):path)}else if(charCode===63||charCode===35)break}return url.slice(start,i)};var getPathNoStrict=(request)=>{let result=getPath(request);return result.length>1&&result.at(-1)==="/"?result.slice(0,-1):result},mergePath=(base,sub,...rest)=>{if(rest.length)sub=mergePath(sub,...rest);return`${base?.[0]==="/"?"":"/"}${base}${sub==="/"?"":`${base?.at(-1)==="/"?"":"/"}${sub?.[0]==="/"?sub.slice(1):sub}`}`},checkOptionalParameter=(path)=>{if(path.charCodeAt(path.length-1)!==63||!path.includes(":"))return null;let segments=path.split("/"),results=[],basePath="";return segments.forEach((segment)=>{if(segment!==""&&!/\:/.test(segment))basePath+="/"+segment;else if(/\:/.test(segment))if(segment.charCodeAt(segment.length-1)===63){if(results.length===0&&basePath==="")results.push("/");else results.push(basePath);let optionalSegment=segment.slice(0,-1);basePath+="/"+optionalSegment,results.push(basePath)}else basePath+="/"+segment}),results.filter((v,i,a)=>a.indexOf(v)===i)},tryDecodeURIComponent=(str)=>str.indexOf("%")!==-1?tryDecode(str,decodeURIComponent_):str,_decodeURI=(value)=>{if(value.indexOf("+")!==-1)value=value.replace(/\+/g," ");return tryDecodeURIComponent(value)},_getQueryParam=(url,key,multiple)=>{let hashIndex=url.indexOf("#",8);if(hashIndex!==-1)url=url.slice(0,hashIndex);let encoded;if(!multiple&&key&&key.indexOf("%")===-1&&key.indexOf("+")===-1){let keyIndex2=url.indexOf("?",8);if(keyIndex2===-1)return;if(!url.startsWith(key,keyIndex2+1))keyIndex2=url.indexOf(`&${key}`,keyIndex2+1);while(keyIndex2!==-1){let trailingKeyCode=url.charCodeAt(keyIndex2+key.length+1);if(trailingKeyCode===61){let valueIndex=keyIndex2+key.length+2,endIndex=url.indexOf("&",valueIndex);return _decodeURI(url.slice(valueIndex,endIndex===-1?void 0:endIndex))}else if(trailingKeyCode==38||isNaN(trailingKeyCode))return"";keyIndex2=url.indexOf(`&${key}`,keyIndex2+1)}if(encoded=/[%+]/.test(url),!encoded)return}let results=Object.create(null);encoded??=/[%+]/.test(url);let keyIndex=url.indexOf("?",8);while(keyIndex!==-1){let nextKeyIndex=url.indexOf("&",keyIndex+1),valueIndex=url.indexOf("=",keyIndex);if(valueIndex>nextKeyIndex&&nextKeyIndex!==-1)valueIndex=-1;let name=url.slice(keyIndex+1,valueIndex===-1?nextKeyIndex===-1?void 0:nextKeyIndex:valueIndex);if(encoded)name=_decodeURI(name);if(keyIndex=nextKeyIndex,name==="")continue;let value;if(valueIndex===-1)value="";else if(value=url.slice(valueIndex+1,nextKeyIndex===-1?void 0:nextKeyIndex),encoded)value=_decodeURI(value);if(multiple){if(!(results[name]&&Array.isArray(results[name])))results[name]=[];results[name].push(value)}else results[name]??=value}return key?results[key]:results},getQueryParam=_getQueryParam,getQueryParams=(url,key)=>_getQueryParam(url,key,!0),decodeURIComponent_=decodeURIComponent;var HonoRequest=class{raw;#validatedData;#matchResult;routeIndex=0;path;bodyCache={};constructor(request,path="/",matchResult=[[]]){this.raw=request,this.path=path,this.#matchResult=matchResult}param(key){return key?this.#getDecodedParam(key):this.#getAllDecodedParams()}#getDecodedParam(key){let paramKey=this.#matchResult[0][this.routeIndex]?.[1][key],param=this.#getParamValue(paramKey);return param&&tryDecodeURIComponent(param)}#getAllDecodedParams(){let decoded={},keys=Object.keys(this.#matchResult[0][this.routeIndex]?.[1]??{});for(let key of keys){let value=this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);if(value!==void 0)decoded[key]=tryDecodeURIComponent(value)}return decoded}#getParamValue(paramKey){return this.#matchResult[1]?this.#matchResult[1][paramKey]:paramKey}query(key){return getQueryParam(this.url,key)}queries(key){return getQueryParams(this.url,key)}header(name){if(name)return this.raw.headers.get(name)??void 0;let headerData=Object.create(null);return this.raw.headers.forEach((value,key)=>{headerData[key]=value}),headerData}async parseBody(options){return parseBody(this,options)}#cachedBody=(key)=>{let{bodyCache,raw}=this,cachedBody=bodyCache[key];if(cachedBody)return cachedBody;for(let anyCachedKey in bodyCache)return bodyCache[anyCachedKey].then((body)=>{if(anyCachedKey==="json")body=JSON.stringify(body);return new Response(body)[key]()});return bodyCache[key]=raw[key]()};json(){return this.#cachedBody("text").then((text)=>JSON.parse(text))}text(){return this.#cachedBody("text")}arrayBuffer(){return this.#cachedBody("arrayBuffer")}bytes(){return this.#cachedBody("arrayBuffer").then((buffer)=>new Uint8Array(buffer))}blob(){return this.#cachedBody("blob")}formData(){return this.#cachedBody("formData")}addValidatedData(target,data){(this.#validatedData??={})[target]=data}valid(target){return this.#validatedData?.[target]}get url(){return this.raw.url}get method(){return this.raw.method}get[GET_MATCH_RESULT](){return this.#matchResult}get matchedRoutes(){return this.#matchResult[0].map(([[,route]])=>route)}get routePath(){return this.#matchResult[0].map(([[,route]])=>route)[this.routeIndex].path}};var HtmlEscapedCallbackPhase={Stringify:1,BeforeStream:2,Stream:3},raw=(value,callbacks)=>{let escapedString=new String(value);return escapedString.isEscaped=!0,escapedString.callbacks=callbacks,escapedString};var resolveCallback=async(str,phase,preserveCallbacks,context,buffer)=>{if(typeof str==="object"&&!(str instanceof String)){if(!(str instanceof Promise))str=str.toString();if(str instanceof Promise)str=await str}let callbacks=str.callbacks;if(!callbacks?.length)return Promise.resolve(str);if(buffer)buffer[0]+=str;else buffer=[str];let resStr=Promise.all(callbacks.map((c)=>c({phase,buffer,context}))).then((res)=>Promise.all(res.filter(Boolean).map((str2)=>resolveCallback(str2,phase,!1,context,buffer))).then(()=>buffer[0]));if(preserveCallbacks)return raw(await resStr,callbacks);else return resStr};var TEXT_PLAIN="text/plain; charset=UTF-8",setDefaultContentType=(contentType,headers)=>({"Content-Type":contentType,...headers}),createResponseInstance=(body,init)=>new Response(body,init),Context=class{#rawRequest;#req;env={};#var;finalized=!1;error;#status;#executionCtx;#res;#layout;#renderer;#notFoundHandler;#preparedHeaders;#matchResult;#path;constructor(req,options){if(this.#rawRequest=req,options)this.#executionCtx=options.executionCtx,this.env=options.env,this.#notFoundHandler=options.notFoundHandler,this.#path=options.path,this.#matchResult=options.matchResult}get req(){return this.#req??=new HonoRequest(this.#rawRequest,this.#path,this.#matchResult),this.#req}get event(){if(this.#executionCtx&&"respondWith"in this.#executionCtx)return this.#executionCtx;else throw Error("This context has no FetchEvent")}get executionCtx(){if(this.#executionCtx)return this.#executionCtx;else throw Error("This context has no ExecutionContext")}get res(){return this.#res||=createResponseInstance(null,{headers:this.#preparedHeaders??=new Headers})}set res(_res){if(this.#res&&_res){_res=createResponseInstance(_res.body,_res);for(let[k,v]of this.#res.headers.entries()){if(k==="content-type")continue;if(k==="set-cookie"){let cookies=this.#res.headers.getSetCookie();_res.headers.delete("set-cookie");for(let cookie of cookies)_res.headers.append("set-cookie",cookie)}else _res.headers.set(k,v)}}this.#res=_res,this.finalized=!0}render=(...args)=>(this.#renderer??=(content)=>this.html(content),this.#renderer(...args));setLayout=(layout)=>this.#layout=layout;getLayout=()=>this.#layout;setRenderer=(renderer)=>{this.#renderer=renderer};header=(name,value,options)=>{if(this.finalized)this.#res=createResponseInstance(this.#res.body,this.#res);let headers=this.#res?this.#res.headers:this.#preparedHeaders??=new Headers;if(value===void 0)headers.delete(name);else if(options?.append)headers.append(name,value);else headers.set(name,value)};status=(status)=>{this.#status=status};set=(key,value)=>{this.#var??=new Map,this.#var.set(key,value)};get=(key)=>this.#var?this.#var.get(key):void 0;get var(){if(!this.#var)return{};return Object.fromEntries(this.#var)}#newResponse(data,arg,headers){let responseHeaders=this.#res?new Headers(this.#res.headers):this.#preparedHeaders;if(typeof arg==="object"&&arg.headers){responseHeaders??=new Headers;for(let[key,value]of new Headers(arg.headers))if(key==="set-cookie")responseHeaders.append(key,value);else responseHeaders.set(key,value)}if(headers){if(!responseHeaders){let count=0;for(let k in headers)if(++count>1||typeof headers[k]!=="string"){responseHeaders=new Headers;break}}if(responseHeaders)for(let k in headers){let v=headers[k];if(typeof v==="string")responseHeaders.set(k,v);else{responseHeaders.delete(k);for(let v2 of v)responseHeaders.append(k,v2)}}}let status=typeof arg==="number"?arg:arg?.status??this.#status;return createResponseInstance(data,{status,headers:responseHeaders??headers})}newResponse=(...args)=>this.#newResponse(...args);body=(data,arg,headers)=>this.#newResponse(data,arg,headers);text=(text,arg,headers)=>!this.#preparedHeaders&&!this.#status&&!arg&&!headers&&!this.finalized?new Response(text):this.#newResponse(text,arg,setDefaultContentType(TEXT_PLAIN,headers));json=(object,arg,headers)=>this.#newResponse(JSON.stringify(object),arg,setDefaultContentType("application/json",headers));html=(html,arg,headers)=>{let res=(html2)=>this.#newResponse(html2,arg,setDefaultContentType("text/html; charset=UTF-8",headers));return typeof html==="object"?resolveCallback(html,HtmlEscapedCallbackPhase.Stringify,!1,{}).then(res):res(html)};redirect=(location,status)=>{let locationString=String(location);return this.header("Location",!/[^\x00-\xFF]/.test(locationString)?locationString:encodeURI(locationString)),this.newResponse(null,status??302)};notFound=()=>(this.#notFoundHandler??=()=>createResponseInstance(),this.#notFoundHandler(this))};var METHOD_NAME_ALL="ALL",METHOD_NAME_ALL_LOWERCASE="all",METHODS=["get","post","put","delete","options","patch","query"],MESSAGE_MATCHER_IS_ALREADY_BUILT="Can not add a route since the matcher is already built.",UnsupportedPathError=class extends Error{};var COMPOSED_HANDLER="__COMPOSED_HANDLER";var notFoundHandler=(c)=>c.text("404 Not Found",404),errorHandler=(err,c)=>{if("getResponse"in err){let res=err.getResponse();return c.newResponse(res.body,res)}return console.error(err),c.text("Internal Server Error",500)},Hono=class _Hono{get;post;put;delete;options;patch;query;all;on;use;router;getPath;_basePath="/";#path="/";routes=[];constructor(options={}){[...METHODS,METHOD_NAME_ALL_LOWERCASE].forEach((method)=>{this[method]=(args1,...args)=>{if(typeof args1==="string")this.#path=args1;else this.#addRoute(method,this.#path,args1);return args.forEach((handler)=>{this.#addRoute(method,this.#path,handler)}),this}}),this.on=(method,path,...handlers)=>{for(let p of[path].flat()){this.#path=p;for(let m of[method].flat())handlers.map((handler)=>{this.#addRoute(m.toUpperCase(),this.#path,handler)})}return this},this.use=(arg1,...handlers)=>{if(typeof arg1==="string")this.#path=arg1;else this.#path="*",handlers.unshift(arg1);return handlers.forEach((handler)=>{this.#addRoute(METHOD_NAME_ALL,this.#path,handler)}),this};let{strict,...optionsWithoutStrict}=options;Object.assign(this,optionsWithoutStrict),this.getPath=strict??!0?options.getPath??getPath:getPathNoStrict}#clone(){let clone=new _Hono({router:this.router,getPath:this.getPath});return clone.errorHandler=this.errorHandler,clone.#notFoundHandler=this.#notFoundHandler,clone.routes=this.routes,clone}#notFoundHandler=notFoundHandler;errorHandler=errorHandler;route(path,app){let subApp=this.basePath(path);return app.routes.map((r)=>{let handler;if(app.errorHandler===errorHandler)handler=r.handler;else handler=async(c,next)=>(await compose([],app.errorHandler)(c,()=>r.handler(c,next))).res,handler[COMPOSED_HANDLER]=r.handler;subApp.#addRoute(r.method,r.path,handler,r.basePath)}),this}basePath(path){let subApp=this.#clone();return subApp._basePath=mergePath(this._basePath,path),subApp}onError=(handler)=>(this.errorHandler=handler,this);notFound=(handler)=>(this.#notFoundHandler=handler,this);mount(path,applicationHandler,options){let replaceRequest,optionHandler;if(options)if(typeof options==="function")optionHandler=options;else if(optionHandler=options.optionHandler,options.replaceRequest===!1)replaceRequest=(request)=>request;else replaceRequest=options.replaceRequest;let getOptions=optionHandler?(c)=>{let options2=optionHandler(c);return Array.isArray(options2)?options2:[options2]}:(c)=>{let executionContext=void 0;try{executionContext=c.executionCtx}catch{}return[c.env,executionContext]};replaceRequest||=(()=>{let mergedPath=mergePath(this._basePath,path),pathPrefixLength=mergedPath==="/"?0:mergedPath.length;return(request)=>{let url=new URL(request.url);return url.pathname=this.getPath(request).slice(pathPrefixLength)||"/",new Request(url,request)}})();let handler=async(c,next)=>{let res=await applicationHandler(replaceRequest(c.req.raw),...getOptions(c));if(res)return res;await next()};return this.#addRoute(METHOD_NAME_ALL,mergePath(path,"*"),handler),this}#addRoute(method,path,handler,baseRoutePath){method=method.toUpperCase(),path=mergePath(this._basePath,path);let r={basePath:baseRoutePath!==void 0?mergePath(this._basePath,baseRoutePath):this._basePath,path,method,handler};this.router.add(method,path,[handler,r]),this.routes.push(r)}#handleError(err,c){if(err instanceof Error)return this.errorHandler(err,c);throw err}#dispatch(request,executionCtx,env,method){if(method==="HEAD")return(async()=>new Response(null,await this.#dispatch(request,executionCtx,env,"GET")))();let path=this.getPath(request,{env}),matchResult=this.router.match(method,path),c=new Context(request,{path,matchResult,env,executionCtx,notFoundHandler:this.#notFoundHandler});if(matchResult[0].length===1){let res;try{res=matchResult[0][0][0][0](c,async()=>{c.res=await this.#notFoundHandler(c)})}catch(err){return this.#handleError(err,c)}return res instanceof Promise?res.then((resolved)=>resolved||(c.finalized?c.res:this.#notFoundHandler(c))).catch((err)=>this.#handleError(err,c)):res??this.#notFoundHandler(c)}let composed=compose(matchResult[0],this.errorHandler,this.#notFoundHandler);return(async()=>{try{let context=await composed(c);if(!context.finalized)throw Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");return context.res}catch(err){return this.#handleError(err,c)}})()}fetch=(request,...rest)=>this.#dispatch(request,rest[1],rest[0],request.method);request=(input,requestInit,Env,executionCtx)=>{if(input instanceof Request)return this.fetch(requestInit?new Request(input,requestInit):input,Env,executionCtx);return input=input.toString(),this.fetch(new Request(/^https?:\/\//.test(input)?input:`http://localhost${mergePath("/",input)}`,requestInit),Env,executionCtx)};fire=()=>{addEventListener("fetch",(event)=>{event.respondWith(this.#dispatch(event.request,event,void 0,event.request.method))})}};var createNullObject=()=>Object.create(null);var emptyParam=[];function match(method,path){let matchers=this.buildAllMatchers(),match2=(method2,path2)=>{let matcher=matchers[method2]||matchers[METHOD_NAME_ALL],staticMatch=matcher[2][path2];if(staticMatch)return staticMatch;let match3=path2.match(matcher[0]);if(!match3)return[[],emptyParam];let index=match3.indexOf("",1);return[matcher[1][index],match3]};return this.match=match2,match2(method,path)}var LABEL_REG_EXP_STR="[^/]+",ONLY_WILDCARD_REG_EXP_STR=".*",TAIL_WILDCARD_REG_EXP_STR="(?:|/.*)",PATH_ERROR=Symbol(),regExpMetaChars=new Set(".\\+*[^]$()");function compareKey(a,b){if(a.length===1)return b.length===1?a<b?-1:1:-1;if(b.length===1)return 1;if(a===ONLY_WILDCARD_REG_EXP_STR||a===TAIL_WILDCARD_REG_EXP_STR)return b===TAIL_WILDCARD_REG_EXP_STR?-1:1;else if(b===ONLY_WILDCARD_REG_EXP_STR||b===TAIL_WILDCARD_REG_EXP_STR)return-1;if(a===LABEL_REG_EXP_STR)return 1;else if(b===LABEL_REG_EXP_STR)return-1;return a.length===b.length?a<b?-1:1:b.length-a.length}var Node=class _Node{#index;#varIndex;#children=createNullObject();insert(tokens,index,paramMap,context,isStatic){let node=this;for(let i=0,len=tokens.length;i<len;i++){let token=tokens[i],pattern=token.length===1?token==="*"?i===len-1?["","",ONLY_WILDCARD_REG_EXP_STR]:["","",LABEL_REG_EXP_STR]:null:token==="/*"?["","",TAIL_WILDCARD_REG_EXP_STR]:token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/),nextNode;if(pattern){let name=pattern[1],regexpStr=pattern[2]||LABEL_REG_EXP_STR;if(name&&pattern[2]){if(regexpStr===".*")throw PATH_ERROR;if(regexpStr=regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/,"(?:"),/\((?!\?:)/.test(regexpStr))throw PATH_ERROR;if(regexpStr.length===1&®ExpMetaChars.has(regexpStr))throw PATH_ERROR}if(nextNode=node.#children[regexpStr],!nextNode){if(regexpStr!==ONLY_WILDCARD_REG_EXP_STR&®expStr!==TAIL_WILDCARD_REG_EXP_STR){for(let k in node.#children)if((regexpStr.length>1||k.length>1)&&k!==ONLY_WILDCARD_REG_EXP_STR&&k!==TAIL_WILDCARD_REG_EXP_STR)throw PATH_ERROR}nextNode=node.#children[regexpStr]=new _Node}if(name!=="")nextNode.#varIndex??=context.varIndex++,paramMap.push([name,nextNode.#varIndex])}else if(nextNode=node.#children[token],!nextNode){for(let k in node.#children)if(k.length>1&&k!==ONLY_WILDCARD_REG_EXP_STR&&k!==TAIL_WILDCARD_REG_EXP_STR)throw PATH_ERROR;nextNode=node.#children[token]=new _Node}node=nextNode}if(node.#index!==void 0)throw PATH_ERROR;node.#index=isStatic?-1:index}buildRegExpStr(){let strList=Object.keys(this.#children).sort(compareKey).map((k)=>{let c=this.#children[k],childStr=c.buildRegExpStr();return childStr===""?"":(typeof c.#varIndex==="number"?`(${k})@${c.#varIndex}`:regExpMetaChars.has(k)?`\\${k}`:k)+childStr}).filter(Boolean);if(typeof this.#index==="number"&&this.#index!==-1)strList.unshift(`#${this.#index}`);if(strList.length===0)return"";if(strList.length===1)return strList[0];return"(?:"+strList.join("|")+")"}};var Trie=class{#context={varIndex:0};#root=new Node;#index=0;paths=createNullObject();insert(path,isStatic){if(isStatic){this.#root.insert(path.split(""),0,[],this.#context,!0);return}let paramAssoc=[],groups=[],markedPath=path;for(let i=0;;){let replaced=!1;if(markedPath=markedPath.replace(/\{[^}]+\}/g,(m)=>{let mark=`@\\${i}`;return groups[i]=[mark,m],i++,replaced=!0,mark}),!replaced)break}let tokens=markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g)||[];for(let i=groups.length-1;i>=0;i--){let[mark]=groups[i];for(let j=tokens.length-1;j>=0;j--)if(tokens[j].indexOf(mark)!==-1){tokens[j]=tokens[j].replace(mark,groups[i][1]);break}}this.#root.insert(tokens,this.#index,paramAssoc,this.#context,!1),this.paths[path]=[this.#index++,paramAssoc]}buildRegExp(){let regexp=this.#root.buildRegExpStr();if(regexp==="")return[/^$/,[],[]];let captureIndex=0,indexReplacementMap=[],paramReplacementMap=[];return regexp=regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g,(_,handlerIndex,paramIndex)=>{if(handlerIndex!==void 0)return indexReplacementMap[++captureIndex]=Number(handlerIndex),"$()";if(paramIndex!==void 0)return paramReplacementMap[Number(paramIndex)]=++captureIndex,"";return""}),[new RegExp(`^${regexp}`),indexReplacementMap,paramReplacementMap]}};var wildcardRegExpCache=createNullObject();function buildWildcardRegExp(path){return wildcardRegExpCache[path]??=new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g,(match2,metaChar)=>metaChar?`\\${metaChar}`:match2==="/*"?TAIL_WILDCARD_REG_EXP_STR:match2==="*"?ONLY_WILDCARD_REG_EXP_STR:`/:${LABEL_REG_EXP_STR}`)}$`)}function findMiddleware(middleware,path){for(let k of Object.keys(middleware).sort((a,b)=>b.length-a.length))if(buildWildcardRegExp(k).test(path))return[...middleware[k]];return}var RegExpRouter=class{name="RegExpRouter";#middleware;#routes;#tries;constructor(){this.#middleware={[METHOD_NAME_ALL]:createNullObject()},this.#routes={[METHOD_NAME_ALL]:createNullObject()},this.#tries={[METHOD_NAME_ALL]:new Trie}}#insertPath(method,path){try{this.#tries[method].insert(path,!/\*|\/:/.test(path))}catch(e){throw e===PATH_ERROR?new UnsupportedPathError(path):e}}add(method,path,handler){let middleware=this.#middleware,routes=this.#routes;if(!middleware)throw Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);if(!middleware[method]){this.#tries[method]=new Trie;for(let handlerMap of[middleware,routes]){handlerMap[method]=createNullObject();for(let p in handlerMap[METHOD_NAME_ALL])handlerMap[method][p]=[...handlerMap[METHOD_NAME_ALL][p]],this.#insertPath(method,p)}}if(path==="/*")path="*";let methods=method===METHOD_NAME_ALL?Object.keys(middleware):[method];if(/\*$/.test(path)){let re=buildWildcardRegExp(path);for(let m of methods)if(!middleware[m][path])this.#insertPath(m,path),middleware[m][path]=findMiddleware(middleware[m],path)||findMiddleware(middleware[METHOD_NAME_ALL],path)||[];for(let handlerMap of[middleware,routes])for(let m of methods)for(let p in handlerMap[m])re.test(p)&&handlerMap[m][p].push([handler,path]);return}let paths=checkOptionalParameter(path)||[path];for(let path2 of paths)for(let m of methods){if(!routes[m][path2])this.#insertPath(m,path2),routes[m][path2]=findMiddleware(middleware[m],path2)||findMiddleware(middleware[METHOD_NAME_ALL],path2)||[];routes[m][path2].push([handler,path2])}}match=match;buildAllMatchers(){let matchers=createNullObject();for(let method of Object.keys(this.#routes))matchers[method]=this.#buildMatcher(method);return this.#middleware=this.#routes=this.#tries=void 0,wildcardRegExpCache=createNullObject(),matchers}#buildMatcher(method){let middleware=this.#middleware[method],routes=this.#routes[method],trie=this.#tries[method],staticMap=createNullObject(),handlerData=[],[regexp,indexReplacementMap,paramReplacementMap]=trie.buildRegExp();for(let r of[middleware,routes])for(let path in r){let handlers=r[path],pathData=trie.paths[path];if(!pathData){staticMap[path]=[handlers.map(([h])=>[h,createNullObject()]),emptyParam];continue}handlerData[pathData[0]]=handlers.map(([h,handlerPath])=>[h,trie.paths[handlerPath][1].reduceRight((map,[key],i)=>(map[key]=paramReplacementMap[pathData[1][i][1]],map),createNullObject())])}return[regexp,indexReplacementMap.map((i)=>handlerData[i]),staticMap]}};var SmartRouter=class{name="SmartRouter";#routers=[];#routes=[];constructor(init){this.#routers=init.routers}add(method,path,handler){if(!this.#routes)throw Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);this.#routes.push([method,path,handler])}match(method,path){if(!this.#routes)throw Error("Fatal error");let routers=this.#routers,routes=this.#routes,len=routers.length,i=0,res;for(;i<len;i++){let router=routers[i];try{for(let i2=0,len2=routes.length;i2<len2;i2++)router.add(...routes[i2]);res=router.match(method,path)}catch(e){if(e instanceof UnsupportedPathError)continue;throw e}this.match=router.match.bind(router),this.#routers=[router],this.#routes=void 0;break}if(i===len)throw Error("Fatal error");return this.name=`SmartRouter + ${this.activeRouter.name}`,res}get activeRouter(){if(this.#routes||this.#routers.length!==1)throw Error("No active router has been determined yet.");return this.#routers[0]}};var emptyParams=createNullObject(),order=0,Node2=class _Node{#methods=[];#children=createNullObject();#patterns=[];#pattern;#params=emptyParams;insert(method,path,handler){let curNode=this,parts=splitRoutingPath(path),possibleKeys=new Set,i=0;for(let p of parts){let nextP=parts[++i],pattern=getPattern(p,nextP)||(nextP===void 0&&p&&p.indexOf("*")===p.length-1?p:null),isParam=Array.isArray(pattern),key=isParam?pattern[0]:pattern||p,child=curNode.#children[key]||=new _Node;if(pattern&&!child.#pattern)child.#pattern=pattern,curNode.#patterns.push(child);if(curNode=child,isParam)possibleKeys.add(pattern[1])}curNode.#methods.push({[method]:{handler,possibleKeys:[...possibleKeys],score:++order}})}#pushHandlerSets(handlerSets,node,method,nodeParams,params){for(let i=0,len=node.#methods.length;i<len;i++){let m=node.#methods[i],handlerSet=m[method]||m[METHOD_NAME_ALL];if(handlerSet){handlerSet.params=createNullObject(),handlerSets.push(handlerSet);for(let i2=0,len2=handlerSet.possibleKeys.length;i2<len2;i2++){let key=handlerSet.possibleKeys[i2];handlerSet.params[key]=params?.[key]&&!i2?params[key]:nodeParams[key]??params?.[key]}}}}search(method,path){let handlerSets=[];this.#params=emptyParams;let curNodes=[this],parts=splitPath(path),curNodesQueue=[],len=parts.length,partOffsets=null;for(let i=0;i<len;i++){let part=parts[i],isLast=i===len-1,tempNodes=[];for(let j=0,len2=curNodes.length;j<len2;j++){let node=curNodes[j],nextNode=node.#children[part];if(nextNode)if(nextNode.#params=node.#params,isLast){if(nextNode.#children["*"])this.#pushHandlerSets(handlerSets,nextNode.#children["*"],method,node.#params);this.#pushHandlerSets(handlerSets,nextNode,method,node.#params)}else tempNodes.push(nextNode);for(let child of node.#patterns){let pattern=child.#pattern,params=node.#params===emptyParams?{}:{...node.#params};if(typeof pattern==="string"){if(pattern==="*"||part.startsWith(pattern.slice(0,-1))){if(this.#pushHandlerSets(handlerSets,child,method,node.#params),pattern==="*")child.#params=params,tempNodes.push(child)}continue}let[,name,matcher]=pattern;if(!part&&matcher===!0)continue;if(matcher!==!0){if(!partOffsets){partOffsets=[];let offset=path[0]==="/"?1:0;for(let p=0;p<len;p++)partOffsets[p]=offset,offset+=parts[p].length+1}let restPathString=path.slice(partOffsets[i]),m=matcher.exec(restPathString);if(m){if(params[name]=m[0],this.#pushHandlerSets(handlerSets,child,method,node.#params,params),m[0].length===restPathString.length&&child.#children["*"])this.#pushHandlerSets(handlerSets,child.#children["*"],method,node.#params,params);for(let _ in child.#children){child.#params=params;let componentCount=m[0].match(/\//g)?.length??0;(curNodesQueue[componentCount]||=[]).push(child);break}continue}}if(matcher===!0||matcher.test(part))if(params[name]=part,isLast){if(this.#pushHandlerSets(handlerSets,child,method,params,node.#params),child.#children["*"])this.#pushHandlerSets(handlerSets,child.#children["*"],method,params,node.#params)}else child.#params=params,tempNodes.push(child)}}let shifted=curNodesQueue.shift();curNodes=shifted?tempNodes.concat(shifted):tempNodes}if(handlerSets[1])handlerSets.sort((a,b)=>a.score-b.score);return[handlerSets.map(({handler,params})=>[handler,params])]}};var TrieRouter=class{name="TrieRouter";#node=new Node2;add(method,path,handler){for(let result of checkOptionalParameter(path)||[path])this.#node.insert(method,result,handler)}match(method,path){return this.#node.search(method,path)}};var Hono2=class extends Hono{constructor(options={}){super(options);this.router=options.router??new SmartRouter({routers:[new RegExpRouter,new TrieRouter]})}};import{AsyncLocalStorage}from"async_hooks";var ACTIVITY_LOG_CAP=250,GROUP_DETAIL_CAP=20,actorStorage=new AsyncLocalStorage;function withActor(actor,fn){return actorStorage.run(actor,fn)}var groupStorage=new AsyncLocalStorage;async function collectActivity(fn){let ops=[];return{value:await groupStorage.run(ops,fn),ops}}function emitGroupedActivity(ctx,verb,ops){if(ops.length===0)return;let first=ops[0];publish(ctx,verb,first.target,{ops:ops.slice(0,GROUP_DETAIL_CAP).map((op)=>({...op,target:enrichBoards(ctx,op.target)})),opCount:ops.length})}function emitActivity(ctx,verb,target){let group=groupStorage.getStore();if(group){group.push({verb,target});return}publish(ctx,verb,target,{})}async function tracked(ctx,verb,target,run){let result=await run();if(result.ok)emitActivity(ctx,verb,typeof target==="function"?target(result.value):target);return result}var logs=new Map,nextId=1;function activityLog(folderRoot){return logs.get(folderRoot)??[]}function enrichBoards(ctx,target){if(!target.screenId||target.boardId||!ctx.folder.boards)return target;let hosting=[...ctx.folder.boards.values()].filter((b)=>b.frames.some((f)=>f.screen===target.screenId)),live=hosting.filter((b)=>!isArchived(b)),boards=(live.length>0?live:hosting).map((b)=>b.id);return boards.length>0?{...target,boards}:target}function publish(ctx,verb,target,extra){let actor=actorStorage.getStore(),event={type:"activity",id:nextId++,ts:Date.now(),verb,source:actor?.source??"cli",...actor?.session?{session:actor.session}:{},target:enrichBoards(ctx,target),...extra},root=ctx.folder.root,log=logs.get(root)??[];if(log.push(event),log.length>ACTIVITY_LOG_CAP)log.splice(0,log.length-ACTIVITY_LOG_CAP);logs.set(root,log),ctx.broadcast(event)}import{readFileSync as readFileSync6}from"fs";import{readFile as readFile9}from"fs/promises";import{homedir as homedir4}from"os";import{join as join17}from"path";import{readFileSync as readFileSync5}from"fs";import{readFile as readFile8}from"fs/promises";async function currentToken(cloud){if(cloud.resolveToken)try{return await cloud.resolveToken()??cloud.token}catch{return cloud.token}return cloud.token}var LOGGED_OUT={loggedIn:!1,verified:null,login:{state:"idle"}};function signInRequired(access,message){return Object.assign(Error(message),{signInRequired:access})}function asSignInRequired(error){if(!error||typeof error!=="object")return null;let marker=error.signInRequired;return marker==="signed-out"||marker==="expired"?marker:null}function boardLimitReached(limit,message){return Object.assign(Error(message),{boardLimit:limit})}function asBoardLimit(error){if(!error||typeof error!=="object")return null;let marker=error.boardLimit;if(!marker||typeof marker.tier!=="string"||typeof marker.limit!=="number")return null;return{tier:marker.tier,limit:marker.limit}}var LOOPBACK_CLOUD_HOSTS=new Set(["localhost","127.0.0.1","::1","[::1]"]);function isSecureCloudUrl(cloudUrl){try{let u=new URL(cloudUrl);if(u.protocol==="https:")return!0;return u.protocol==="http:"&&LOOPBACK_CLOUD_HOSTS.has(u.hostname)}catch{return!1}}var SharedCacheSchema=object({version:literal(1),folderId:string().min(1),threads:array(CommentThreadSchema),links:record(string(),_enum(["ok","revoked"])),refreshedAt:exports_iso.datetime()}),OwnerFeedSchema=object({threads:array(CommentThreadSchema),links:record(string(),_enum(["ok","revoked"])),now:exports_iso.datetime()}),FETCH_TIMEOUT_MS=1e4;async function cloudFailure2(response){let body=await response.json().catch(()=>{return});if(body&&typeof body==="object"&&"message"in body){let{message,error}=body;if(typeof message==="string"&&message)return isCloudErrorCode(error)?`${message} (${error})`:message}return`Cloud comment request failed (${response.status}).`}function signature(threads){return threads.map((thread)=>`${thread.id}:${thread.updatedAt}:${thread.status}:${thread.messages.length}`).sort().join("|")}class SharedCommentsClient{folderId;cloud;cachePath;tail=Promise.resolve();constructor(folderId,cloud,cachePath){this.folderId=folderId;this.cloud=cloud;this.cachePath=cachePath}async cached(){let id=this.folderId();if(!id)return[];try{let parsed=SharedCacheSchema.parse(JSON.parse(await readFile8(this.cachePath(),"utf8")));return parsed.folderId===id?parsed.threads:[]}catch{return[]}}cachedSync(){let id=this.folderId();if(!id)return[];try{let parsed=SharedCacheSchema.parse(JSON.parse(readFileSync5(this.cachePath(),"utf8")));return parsed.folderId===id?parsed.threads:[]}catch{return[]}}refresh(){let run=this.tail.then(()=>this.doRefresh(),()=>this.doRefresh());return this.tail=run.then(()=>{return},()=>{return}),run}async doRefresh(){let folderId=this.folderId(),before=await this.cached();if(!folderId)return{status:"unpublished",changed:!1,boardIds:[],threads:before};let token=await currentToken(this.cloud);if(!token)return{status:"logged-out",changed:!1,boardIds:[],threads:before};try{let query=new URLSearchParams({folderId,status:"all"}),response=await fetch(`${this.cloud.url}/v1/comment-threads?${query}`,{headers:{authorization:`Bearer ${token}`},signal:AbortSignal.timeout(FETCH_TIMEOUT_MS)});if(!response.ok)throw Error(`shared comment fetch failed (${response.status})`);let payload=OwnerFeedSchema.parse(await response.json()),changed=signature(before)!==signature(payload.threads);await writeJsonAtomic(this.cachePath(),{version:1,folderId,threads:payload.threads,links:payload.links,refreshedAt:payload.now});let boardIds=changed?[...new Set([...before,...payload.threads].map((thread)=>thread.boardId))]:[];return{status:"ok",changed,boardIds,threads:payload.threads}}catch{return{status:"offline",changed:!1,boardIds:[],threads:before}}}async get(id){return(await this.refresh()).threads.find((thread)=>thread.id===id)}async mutate(path,init){let token=await currentToken(this.cloud);if(!token)throw Error("Sign in to write shared comments.");let response=await fetch(`${this.cloud.url}${path}`,{...init,headers:{"content-type":"application/json",authorization:`Bearer ${token}`,...init.headers},signal:AbortSignal.timeout(FETCH_TIMEOUT_MS)});if(!response.ok)throw Error(await cloudFailure2(response));let parsed=object({thread:CommentThreadSchema}).parse(await response.json());return await this.refresh(),parsed.thread}create(slug,input){return this.mutate(`/v1/links/${encodeURIComponent(slug)}/comment-threads`,{method:"POST",body:JSON.stringify(input)})}reply(id,body,authorKind){return this.mutate(`/v1/comment-threads/${encodeURIComponent(id)}/messages`,{method:"POST",body:JSON.stringify({body,authorKind})})}setResolved(id,resolved){return this.mutate(`/v1/comment-threads/${encodeURIComponent(id)}`,{method:"PATCH",body:JSON.stringify({resolved})})}deleteMessage(id,messageId){let path=`/v1/comment-threads/${encodeURIComponent(id)}/messages/${encodeURIComponent(messageId)}`;return this.mutate(path,{method:"DELETE"})}}function pathAt(root,path){let node=root;for(let idx of path){if(!node||!isComponentNode(node))return null;if(!node.children||idx<0||idx>=node.children.length)return null;node=node.children[idx]}return node??null}function parentOf(path){if(path.length===0)return null;let last=path[path.length-1];return{parent:path.slice(0,-1),index:last}}function isIdLocator(value){return typeof value==="string"&&value.startsWith("@")&&value.length>1}function findById(root,id){function walk(node,path){if(nodeId(node)===id)return path;if(isSnippetInstance(node))return null;if(!isComponentNode(node))return null;if(!node.children)return null;for(let i=0;i<node.children.length;i++){let child=node.children[i];if(!child)continue;let found=walk(child,[...path,i]);if(found)return found}return null}return walk(root,[])}function resolveLocator(root,locator){let loc=locator;if(typeof loc==="string"&&loc.trim().startsWith("[")&&loc.trim().endsWith("]"))try{let parsed=JSON.parse(loc.trim());if(Array.isArray(parsed)&&parsed.every((n)=>Number.isInteger(n)&&n>=0))loc=parsed}catch{}if(Array.isArray(loc))return pathAt(root,loc)===null?null:loc;if(isIdLocator(loc))return findById(root,loc.slice(1));return null}var LocalCommentsFileSchema=object({version:literal(1),folderId:string().min(1),threads:array(CommentThreadSchema)});class CommentStoreError extends Error{code;constructor(code,message){super(message);this.code=code}}function newestPublishedSlot(slots,boardId){return slots.filter((slot)=>slot.latestVersionId!==null&&slot.context.boardIds.includes(boardId)).sort((left,right)=>{let leftAt=left.lastPublishedAt?Date.parse(left.lastPublishedAt):0;return(right.lastPublishedAt?Date.parse(right.lastPublishedAt):0)-leftAt||left.slug.localeCompare(right.slug)})[0]}var defaultAuthor=()=>({kind:"user"});function ownerAuthorKind(author){return author?.kind==="agent"?"agent":"user"}function folderIdentity(ctx){return ctx.folder.config.folderId??Bun.CryptoHasher.hash("sha256",ctx.folder.root,"hex")}function localCommentsPath(folderId){let override=process.env.VELLOO_COMMENTS_PATH;if(override)return override;let safe=folderId.replace(/[^A-Za-z0-9_-]/g,"_");return join17(process.env.VELLOO_COMMENTS_DIR??join17(homedir4(),".velloo","comments"),`${safe}.json`)}function message(body,author,now){return{id:crypto.randomUUID(),author:CommentAuthorSchema.parse(author),body:body.trim(),createdAt:now}}class LocalCommentsService{ctxFor;storePath;publish;tail=Promise.resolve();shared;constructor(ctxFor,storePath,cloud,publish){this.ctxFor=ctxFor;this.storePath=storePath;this.publish=publish;if(cloud)this.shared=new SharedCommentsClient(()=>this.ctxFor().folder.config.folderId,cloud,()=>`${this.path()}.shared`)}async cloudAvailability(boardId){if(!this.shared||!this.publish)return{available:!1,reason:"unsupported"};let access=await this.publish.access().catch(()=>({state:"signed-out"}));if(access.state!=="ready")return{available:!1,reason:access.state};let slots=await this.publish.destinations().then((value)=>value.slots).catch(()=>[]),slot=newestPublishedSlot(slots,boardId);if(!slot)return{available:!1,reason:"unpublished"};return{available:!0,slug:slot.slug,url:slot.url}}async cloudTarget(boardId){if(!this.shared||!this.publish)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let access=await this.publish.access().catch(()=>({state:"signed-out"}));if(access.state!=="ready")throw new CommentStoreError("invalid",access.state==="expired"?"Your velloo-cloud session ended \u2014 sign in again to write cloud comments.":"Sign in to velloo cloud to write cloud comments.");let slots=await this.publish.destinations().then((value)=>value.slots).catch(()=>[]),slot=newestPublishedSlot(slots,boardId);if(!slot?.latestVersionId)throw new CommentStoreError("invalid","Publish this board before commenting on the cloud.");return{slug:slot.slug,versionId:slot.latestVersionId}}path(){return this.storePath??localCommentsPath(folderIdentity(this.ctxFor()))}async read(){let folderId=folderIdentity(this.ctxFor());try{let parsed=LocalCommentsFileSchema.parse(JSON.parse(await readFile9(this.path(),"utf8")));if(parsed.folderId===folderId)return parsed}catch(error){if(error.code!=="ENOENT"&&!(error instanceof SyntaxError))throw error}return{version:1,folderId,threads:[]}}async write(file){await writeJsonAtomic(this.path(),LocalCommentsFileSchema.parse(file))}serialized(fn){let run=this.tail.then(fn,fn);return this.tail=run.then(()=>{return},()=>{return}),run}validateBoardAndAnchor(boardId,anchor){let folder=this.ctxFor().folder,board=folder.boards.get(boardId);if(!board)throw new CommentStoreError("invalid",`No board named ${boardId}.`);if(!anchor)return;let parsed=CommentAnchorSchema.parse(anchor);if(parsed.boardId!==boardId)throw new CommentStoreError("invalid","The anchor belongs to a different board.");if(parsed.kind==="board")return;let frame=board.frames.find((candidate)=>candidate.id===parsed.frameId);if(!frame||frame.screen!==parsed.screenId)throw new CommentStoreError("invalid","The anchor frame is not on this board or screen.");if(!folder.screens.has(parsed.screenId))throw new CommentStoreError("invalid",`No screen named ${parsed.screenId}.`)}view(thread){let anchor=thread.anchor;if(!anchor||anchor.kind==="board")return{...thread,anchorState:{status:"board"}};let folder=this.ctxFor().folder,board=folder.boards.get(thread.boardId),frame=board?.frames.find((candidate)=>candidate.id===anchor.frameId),screen=folder.screens.get(anchor.screenId);if(!board||!frame||frame.screen!==anchor.screenId||!screen)return{...thread,anchorState:{status:"stale"}};let resolvedPath=resolveLocator(screen.tree,anchor.locator);return resolvedPath?{...thread,anchorState:{status:"attached",resolvedPath}}:{...thread,anchorState:{status:"stale"}}}async sharedThreads(){return this.shared?(await this.shared.refresh()).threads:[]}async refreshShared(){let result=await this.shared?.refresh();if(result?.changed)for(let boardId of result.boardIds)this.ctxFor().broadcast({type:"comments-changed",boardId,scope:"shared"});return result}async list(boardId,status="open",scope="all"){if(!this.ctxFor().folder.boards.has(boardId))throw new CommentStoreError("invalid",`No board named ${boardId}.`);let[file,shared]=await Promise.all([this.read(),this.sharedThreads()]);return[...file.threads,...shared].filter((thread)=>thread.boardId===boardId&&(status==="all"||thread.status===status)&&(scope==="all"||thread.scope===scope)).sort((a,b)=>b.updatedAt.localeCompare(a.updatedAt)).map((thread)=>this.view(thread))}async listAll(status="open",scope="all"){let[file,shared]=await Promise.all([this.read(),this.sharedThreads()]);return[...file.threads,...shared].filter((thread)=>(status==="all"||thread.status===status)&&(scope==="all"||thread.scope===scope)).sort((a,b)=>b.updatedAt.localeCompare(a.updatedAt)).map((thread)=>this.view(thread))}countOpenSync(){let shared=this.shared?.cachedSync().filter((thread)=>thread.status==="open").length??0;try{let parsed=LocalCommentsFileSchema.parse(JSON.parse(readFileSync6(this.path(),"utf8")));return(parsed.folderId===folderIdentity(this.ctxFor())?parsed.threads.filter((thread)=>thread.status==="open").length:0)+shared}catch{return shared}}async get(id){let thread=(await this.read()).threads.find((candidate)=>candidate.id===id);if(thread)return this.view(thread);let shared=await this.shared?.get(id);if(shared)return this.view(shared);throw new CommentStoreError("not-found","No such comment thread.")}create(input){return this.serialized(async()=>{this.validateBoardAndAnchor(input.boardId,input.anchor);let body=input.body.trim();if(!body)throw new CommentStoreError("invalid","Comment body is required.");let author=CommentAuthorSchema.parse(input.author??defaultAuthor());if(input.scope==="shared")return this.view(await this.createShared(input.boardId,body,input.anchor,ownerAuthorKind(author)));let now=new Date().toISOString(),file=await this.read(),thread=CommentThreadSchema.parse({id:crypto.randomUUID(),scope:"local",folderId:file.folderId,boardId:input.boardId,...input.anchor?{anchor:input.anchor}:{},origin:{kind:"local"},status:"open",messages:[message(body,author,now)],createdAt:now,updatedAt:now});return file.threads.push(thread),await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:thread.boardId,scope:"local"}),this.view(thread)})}reply(id,input){return this.serialized(async()=>{let body=input.body.trim();if(!body)throw new CommentStoreError("invalid","Reply body is required.");let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.reply(id,body,ownerAuthorKind(input.author));return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],now=new Date().toISOString(),next=CommentThreadSchema.parse({...current,messages:[...current.messages,message(body,input.author??defaultAuthor(),now)],updatedAt:now});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}setResolved(id,resolved){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.setResolved(id,resolved);return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],now=new Date().toISOString(),next=CommentThreadSchema.parse({...current,status:resolved?"resolved":"open",updatedAt:now,...resolved?{resolvedAt:now}:{resolvedAt:void 0}});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}async createShared(boardId,body,anchor,authorKind){let shared=this.shared;if(!shared)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let target=await this.cloudTarget(boardId),thread=await shared.create(target.slug,{versionId:target.versionId,boardId,...anchor?{anchor}:{},body,authorKind}).catch((error)=>{throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))});return this.ctxFor().broadcast({type:"comments-changed",boardId,scope:"shared"}),thread}promoteToShared(id){return this.serialized(async()=>{let shared=this.shared;if(!shared)throw new CommentStoreError("invalid","This canvas cannot write cloud comments.");let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(await this.shared?.get(id))throw new CommentStoreError("invalid","This thread is already a cloud thread.");throw new CommentStoreError("not-found","No such comment thread.")}let local=file.threads[index],[first,...rest]=local.messages;if(!first)throw new CommentStoreError("invalid","That thread has no messages to move.");let promoted=await this.createShared(local.boardId,first.body,local.anchor,ownerAuthorKind(first.author));try{for(let message of rest)promoted=await shared.reply(promoted.id,message.body,ownerAuthorKind(message.author));if(local.status==="resolved")promoted=await shared.setResolved(promoted.id,!0)}catch(error){throw new CommentStoreError("invalid",`The thread moved to the cloud but its replies did not: ${error instanceof Error?error.message:String(error)}`)}let latest=await this.read();return latest.threads=latest.threads.filter((candidate)=>candidate.id!==id),await this.write(latest),this.ctxFor().broadcast({type:"comments-changed",boardId:local.boardId,scope:"local"}),this.view(promoted)})}deleteMessage(id,messageId){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(!this.shared)throw new CommentStoreError("not-found","No such comment thread.");try{let next=await this.shared.deleteMessage(id,messageId);return this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"shared"}),this.view(next)}catch(error){throw new CommentStoreError("invalid",error instanceof Error?error.message:String(error))}}let current=file.threads[index],remaining=current.messages.filter((candidate)=>candidate.id!==messageId);if(remaining.length===current.messages.length)throw new CommentStoreError("not-found","No such comment.");if(remaining.length===0)throw new CommentStoreError("invalid","That is the thread's only comment \u2014 delete the thread instead.");let next=CommentThreadSchema.parse({...current,messages:remaining,updatedAt:new Date().toISOString()});return file.threads[index]=next,await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:next.boardId,scope:"local"}),this.view(next)})}delete(id){return this.serialized(async()=>{let file=await this.read(),index=file.threads.findIndex((candidate)=>candidate.id===id);if(index<0){if(await this.shared?.get(id))throw new CommentStoreError("invalid","Shared comment threads cannot be deleted. Resolve the conversation instead.");throw new CommentStoreError("not-found","No such comment thread.")}let[removed]=file.threads.splice(index,1);if(!removed)throw new CommentStoreError("not-found","No such comment thread.");return await this.write(file),this.ctxFor().broadcast({type:"comments-changed",boardId:removed.boardId,scope:"local"}),{removedId:id,boardId:removed.boardId}})}}import{readFile as readFile10,unlink}from"fs/promises";import{join as join18}from"path";var assetsFilePath=(root)=>join18(root,"assets.json");async function readAssetsFile(root){let raw;try{raw=await readFile10(assetsFilePath(root),"utf8")}catch{return EMPTY_ASSETS_FILE}try{let parsed=AssetsFileSchema.safeParse(JSON.parse(raw));return parsed.success?parsed.data:EMPTY_ASSETS_FILE}catch{return EMPTY_ASSETS_FILE}}var writeChains=new Map;function serialize(root,op){let next=(writeChains.get(root)??Promise.resolve()).then(op,op);return writeChains.set(root,next.catch(()=>{})),next}function assetReferences(folder,assetPath){let url=`/${assetPath}`,hits=[],uses=(value)=>{let json=JSON.stringify(value)??"";return json.includes(`"${url}"`)||json.includes(`"${assetPath}"`)};for(let[id,screen]of folder.screens)if(uses(screen))hits.push(`screen "${id}"`);for(let[id,snippet]of folder.snippets)if(uses(snippet))hits.push(`snippet "${id}"`);return hits}async function deleteGeneratedAsset(folder,assetPath){let root=folder.root;if(!assetPath.startsWith("assets/")||assetPath.includes(".."))return{ok:!1,reason:`"${assetPath}" is not a path inside this folder's asset store.`};let used=assetReferences(folder,assetPath);if(used.length>0)return{ok:!1,reason:`${assetPath} is still used by ${used.join(", ")} \u2014 point those at another image first.`};return await unlink(join18(root,assetPath)).catch(()=>{}),await serialize(root,async()=>{let file=await readAssetsFile(root);if(!(assetPath in file.generated))return;let{[assetPath]:_dropped,...rest}=file.generated;await writeJsonAtomic(assetsFilePath(root),{...file,generated:rest})}),{ok:!0}}async function recordGeneratedAssets(root,entries){if(Object.keys(entries).length===0)return;await serialize(root,async()=>{let file=await readAssetsFile(root);await writeJsonAtomic(assetsFilePath(root),{...file,generated:{...file.generated,...entries}})})}import{readFile as readFile11}from"fs/promises";import{isAbsolute as isAbsolute2,join as join19,normalize as normalize2,relative as relative3,resolve as resolve8}from"path";var INTENTS=["photo","illustration","graphic","texture","icon","vector","mark","edit","cutout","upscale"],ASPECTS=["1:1","4:3","3:4","16:9","9:16","3:2","2:3"],FETCH_TIMEOUT_MS2=120000,MAX_REFERENCE_BYTES=4194304,MAX_REFERENCES=3,REFERENCE_MIME={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif"},NOT_AVAILABLE="Hosted generation isn't available on this velloo-cloud server.",HINTS={400:"Fix the arguments and retry \u2014 nothing was generated or charged.",402:"Ask the user to top up credits before retrying.",404:NOT_AVAILABLE,429:"The limit is per-account per minute \u2014 wait a minute, then retry.",502:"Retry once.",503:NOT_AVAILABLE};function formatDollars(micros){let value=micros/1e6,fixed=value.toFixed(2);return Number(fixed)===value?`$${fixed}`:`$${value}`}function describeGenerateFailure(error){if(error.kind==="Unreachable")return`${describeCloudError(error)}. Nothing was generated or charged.`;if(error.kind!=="HttpFailure")return describeCloudError(error);let hint=HINTS[error.status],detail=error.detail==="unknown"?`velloo-cloud rejected the generation request (${error.status}).`:error.detail.trimEnd(),sentence=/[.!?]$/.test(detail)?detail:`${detail}.`;return hint?`${sentence} ${hint}`:sentence}var DATA_URL=/^data:image\/(png|svg\+xml);base64,([A-Za-z0-9+/=\s]+)$/;async function encodeReference(root,ref){let cleaned=ref.replace(/^\/+/,"");if(isAbsolute2(cleaned)||normalize2(cleaned).startsWith(".."))return err(invalidRequest(`Reference "${ref}" is outside the design folder \u2014 pass a folder-relative path like "assets/hero.png".`));let full=resolve8(join19(root,cleaned)),rel=relative3(resolve8(root),full);if(rel.startsWith("..")||isAbsolute2(rel))return err(invalidRequest(`Reference "${ref}" is outside the design folder \u2014 pass a folder-relative path like "assets/hero.png".`));let ext=(full.split(".").pop()??"").toLowerCase(),mime=REFERENCE_MIME[ext];if(!mime)return err(invalidRequest(`Reference "${ref}" isn't a supported image (expected ${Object.keys(REFERENCE_MIME).join(", ")}).`));let bytes;try{bytes=await readFile11(full)}catch{return err(invalidRequest(`Reference "${ref}" doesn't exist in this design folder.`))}if(bytes.length>MAX_REFERENCE_BYTES)return err(invalidRequest(`Reference "${ref}" is ${(bytes.length/1048576).toFixed(1)} MB \u2014 the limit is ${MAX_REFERENCE_BYTES/1048576} MB. Use a smaller source image.`));return ok(`data:${mime};base64,${bytes.toString("base64")}`)}async function fetchIntentCatalog(cloud){let token=await currentToken(cloud);if(!token)return err(loggedOut("Not signed in to velloo-cloud"));if(!isSecureCloudUrl(cloud.url))return err(insecureCloudUrl(cloud.url));return cloudFetch(`${cloud.url}/v1/assets/intents`,IntentCatalogResponseSchema,{operation:"reading the generation catalogue",token,timeoutMs:1e4})}async function generateAsset(root,cloud,req){let token=await currentToken(cloud);if(!token)return err(loggedOut("Not signed in to velloo-cloud"));if(!isSecureCloudUrl(cloud.url))return err(insecureCloudUrl(cloud.url));let refPaths=req.reference??[];if(refPaths.length>MAX_REFERENCES)return err(invalidRequest(`At most ${MAX_REFERENCES} reference images per generation (got ${refPaths.length}).`));let references=[];for(let ref of refPaths){let encoded=await encodeReference(root,ref);if(!encoded.ok)return encoded;references.push(encoded.value)}let res;try{res=await fetch(`${cloud.url}/v1/assets/generate`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${token}`},body:JSON.stringify({prompt:req.prompt,intent:req.intent,...req.aspect?{aspect:req.aspect}:{},...req.count&&req.count>1?{count:req.count}:{},...references.length>0?{reference:references}:{}}),signal:AbortSignal.timeout(FETCH_TIMEOUT_MS2)})}catch(e){return err(unreachable(e,{url:cloud.url}))}if(!res.ok){let{detail,code}=await readFailure(res);return err(cloudFailure("generation",res.status,detail,code))}let parsed=await cloudJson(res,GenerateResponseSchema,"generation");if(!parsed.ok)return parsed;let payload=parsed.value,stem=req.filename?.replace(/\.(png|svg)$/i,"")??payload.id,files=[];for(let[i,asset]of payload.assets.entries()){let match=DATA_URL.exec(asset.dataUrl);if(!match)return err(protocolViolation("velloo-cloud returned a data URL this server doesn't understand (expected base64 png or svg)"));let ext=match[1]==="png"?"png":"svg",bytes=Buffer.from(match[2],"base64");if(ext==="svg"&&svgLooksActive(bytes.toString("utf8")))return err(protocolViolation("velloo-cloud returned SVG containing active content (script/event handlers) \u2014 refusing to store it; retry the generation"));let name=payload.assets.length===1?stem:`${stem}-${i+1}`,stored=await storeAsset(root,`${name}.${ext}`,bytes);files.push({...stored,kind:asset.kind,...ext==="png"?pngSize2(bytes)??{}:{},...ext==="svg"?{content:bytes.toString("utf8")}:{}})}await recordGeneratedAssets(root,Object.fromEntries(files.map((f)=>[f.assetPath,{prompt:req.prompt,intent:req.intent,...req.aspect?{aspect:req.aspect}:{},...f.width&&f.height?{width:f.width,height:f.height}:{},generatedAt:new Date().toISOString(),...refPaths.length>0?{reference:refPaths}:{},...req.replaces?{replaces:req.replaces}:{}}]))).catch(()=>{});let first=files[0];return ok({assets:files,intent:req.intent,chargedMicros:payload.chargedMicros,balanceMicros:payload.balanceMicros,cost:`cost ${formatDollars(payload.chargedMicros)}, balance ${formatDollars(payload.balanceMicros)}`,assetPath:first.assetPath,url:first.url,kind:first.kind,...first.width&&first.height?{width:first.width,height:first.height}:{},...first.content?{content:first.content}:{}})}var GenerateBody=object({prompt:string().min(1).max(2000),intent:_enum(INTENTS),aspect:_enum(ASPECTS).optional(),reference:array(string()).max(3).optional(),replaces:string().max(512).optional()});function createAssetsRouter(folder,cloud){let app=new Hono2;app.get("/",async(c)=>{let file=await readAssetsFile(folder().root);return c.json({generated:file.generated})});let fail=(c,status,kind,message)=>c.json({error:{kind,message}},status);return app.get("/intents",async(c)=>{if(!cloud)return c.json({intents:[]});let r=await fetchIntentCatalog(cloud);return c.json(r.ok?r.value:{intents:[]})}),app.post("/delete",async(c)=>{let body=await c.req.json().catch(()=>{return});if(typeof body?.assetPath!=="string")return fail(c,400,"BadRequest","assetPath is required.");let r=await deleteGeneratedAsset(folder(),body.assetPath);if(!r.ok)return fail(c,409,"AssetInUse",r.reason);return c.json({deleted:body.assetPath})}),app.post("/generate",async(c)=>{if(!cloud)return fail(c,503,"LoggedOut","This daemon has no velloo-cloud connection \u2014 run `velloo login`, then reopen the canvas.");let parsed=GenerateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return fail(c,400,"BadRequest",parsed.error.issues[0]?.message??"invalid request");let r=await generateAsset(folder().root,cloud,parsed.data);if(!r.ok){let status=r.error.kind==="LoggedOut"?401:r.error.kind==="InvalidRequest"?400:r.error.kind==="HttpFailure"?r.error.status:502;return fail(c,status,r.error.kind,describeGenerateFailure(r.error))}return c.json(r.value)}),app}function createAuthRouter(auth){let app=new Hono2;return app.get("/status",async(c)=>{if(!auth)return c.json(LOGGED_OUT);return c.json(await auth.status())}),app.post("/login",async(c)=>{if(!auth)return c.json({error:"this server has no credential store to sign in to"},503);return c.json(await auth.beginLogin())}),app.post("/login/cancel",async(c)=>{if(auth)await auth.cancelLogin();return c.json({ok:!0})}),app.post("/logout",async(c)=>{if(auth)await auth.logout();return c.json({ok:!0})}),app}function createLiveRouter(bundler){let r=new Hono2,headers={"Content-Type":"text/javascript; charset=utf-8","Cache-Control":"no-store","Access-Control-Allow-Origin":"*"};return r.get("/bundle.js",async(c)=>{let{code,errors}=await bundler.build(),body=errors.length?`${code}
|
|
154
154
|
export const __velloo_live_error = ${JSON.stringify(errors)};
|
|
155
155
|
`:code;return c.body(body,200,headers)}),r.get("/bundle-app.js",async(c)=>{let{code,errors}=await bundler.buildApp(c.req.query("app")??""),body=errors.length?`${code}
|
|
156
156
|
export const __velloo_live_error = ${JSON.stringify(errors)};
|
|
157
157
|
`:code;return c.body(body,200,headers)}),r}function createCanvasRouter(bundler,defaultLibraryId){let r=new Hono2;return r.get("/bundle.js",async(c)=>{let lib=c.req.query("lib")||defaultLibraryId(),refs=(c.req.query("refs")??"").split(",").filter(Boolean),{code,errors}=await bundler.build(lib,refs),body=errors.length?`${code}
|
|
158
158
|
export const __velloo_canvas_build_errors = ${JSON.stringify(errors)};
|
|
159
|
-
`:code;return c.body(body,200,{"Content-Type":"text/javascript; charset=utf-8","Cache-Control":"no-store","Access-Control-Allow-Origin":"*"})}),r.get("/status",async(c)=>{let lib=c.req.query("lib")||defaultLibraryId(),refs=(c.req.query("refs")??"").split(",").filter(Boolean),result=await bundler.build(lib,refs);return c.json({usable:result.usable,diagnostics:result.diagnostics,errors:result.errors})}),r}import{readFileSync as readFileSync5}from"fs";import{join as join19}from"path";var CONTENT_TYPES={png:"image/png",json:"application/json",mhtml:"multipart/related",svg:"image/svg+xml",jpg:"image/jpeg",jpeg:"image/jpeg",webp:"image/webp",gif:"image/gif",css:"text/css"};function contentType(file){let ext=file.split(".").pop()?.toLowerCase()??"";return CONTENT_TYPES[ext]??"application/octet-stream"}function createCapturesRouter(folder){let r=new Hono2,rootOf=()=>{let f=folder(),folderId=f.config.folderId;return folderId?{root:f.root,folderId}:{root:f.root}};return r.get("/",(c)=>{let{root,folderId}=rootOf();return c.json({captures:listCaptures(root,folderId)})}),r.get("/:id",(c)=>{let id=c.req.param("id");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let{root,folderId}=rootOf(),manifest=readCaptureManifest(root,id,folderId);if(!manifest)return c.json({error:"capture not found"},404);return c.json(manifest)}),r.get("/:id/:file{.+}",(c)=>{let id=c.req.param("id"),rel=c.req.param("file");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let parts=rel.split("/");if(parts.length>2||!parts.every((p)=>isSafeCaptureFile(p)))return c.json({error:"bad capture file"},400);let{root,folderId}=rootOf();try{let body=readFileSync5(join19(captureDir(root,id,folderId),...parts));return c.body(new Uint8Array(body),200,{"Content-Type":contentType(parts[parts.length-1]??""),"Cache-Control":"no-store"})}catch{return c.json({error:"capture file not found"},404)}}),r.delete("/:id",(c)=>{let id=c.req.param("id");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let{root,folderId}=rootOf();return deleteCapture(root,id,folderId)?c.json({ok:!0,id}):c.json({error:"capture not found"},404)}),r}var CreateBody=object({boardId:string().min(1),body:string().min(1).max(4000),anchor:CommentAnchorSchema.optional(),author:CommentAuthorSchema.optional(),scope:_enum(["local","shared"]).optional()}),ReplyBody=object({body:string().min(1).max(4000),author:CommentAuthorSchema.optional()}),UpdateBody=object({resolved:boolean().optional(),scope:literal("shared").optional()}).refine((value)=>value.resolved!==void 0||value.scope!==void 0);function createCommentsRouter(service){let r=new Hono2,respond=async(c,op)=>{try{return c.json(await op())}catch(error){if(error instanceof CommentStoreError)return c.json({error:{code:error.code,message:error.message}},error.code==="not-found"?404:400);if(error instanceof ZodError)return c.json({error:{code:"invalid",message:error.message}},400);throw error}};return r.get("/",(c)=>{let boardId=c.req.query("boardId")??"",statusParam=c.req.query("status")??"open",status=statusParam==="resolved"||statusParam==="all"?statusParam:"open",scopeParam=c.req.query("scope")??"all",scope=scopeParam==="local"||scopeParam==="shared"?scopeParam:"all";return respond(c,async()=>({threads:await service.list(boardId,status,scope)}))}),r.get("/cloud",(c)=>respond(c,()=>service.cloudAvailability(c.req.query("boardId")??""))),r.get("/:id",(c)=>respond(c,async()=>({thread:await service.get(c.req.param("id"))}))),r.post("/",async(c)=>{let parsed=CreateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>({thread:await service.create(parsed.data)}))}),r.post("/:id/messages",async(c)=>{let parsed=ReplyBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>({thread:await service.reply(c.req.param("id"),parsed.data)}))}),r.patch("/:id",async(c)=>{let parsed=UpdateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>{let thread=await service.get(c.req.param("id"));if(parsed.data.resolved!==void 0)thread=await service.setResolved(thread.id,parsed.data.resolved);if(parsed.data.scope==="shared"&&thread.scope==="local")thread=await service.promoteToShared(thread.id);return{thread}})}),r.delete("/:id/messages/:messageId",(c)=>respond(c,async()=>({thread:await service.deleteMessage(c.req.param("id"),c.req.param("messageId"))}))),r.delete("/:id",(c)=>respond(c,()=>service.delete(c.req.param("id")))),r}import{existsSync as existsSync6}from"fs";import{readFile as readFile12}from"fs/promises";import{homedir as homedir5}from"os";import{basename as basename4,dirname as dirname12,join as join20}from"path";function findSnippetInstances(folder,snippetId){let out=[];function walk(node,path,screenIdLabel){if(isSnippetInstance(node)){if(node.$snippet===snippetId)out.push({screenId:screenIdLabel,path:path.join("."),hasOverride:typeof node.$extraClassName==="string"&&node.$extraClassName!=="",args:node.args??{}});return}if(!isComponentNode(node))return;if(!node.children)return;for(let i=0;i<node.children.length;i++){let child=node.children[i];if(child)walk(child,[...path,i],screenIdLabel)}}for(let[id,screen]of folder.screens)walk(screen.tree,[],id);for(let[id,snippet]of folder.snippets){if(id===snippetId)continue;walk(snippet.tree,[],`snippet:${id}`)}return out}function snippetIdsIn(value,out=new Set){if(value===null||typeof value!=="object")return out;if(Array.isArray(value)){for(let item of value)snippetIdsIn(item,out);return out}let record=value;if(typeof record.$snippet==="string")out.add(record.$snippet);for(let nested of Object.values(record))snippetIdsIn(nested,out);return out}function reachableSnippetIds(folder,roots){let reached=new Set,pending=[],push=(from)=>{for(let id of snippetIdsIn(from)){if(reached.has(id))continue;reached.add(id),pending.push(id)}};for(let screen of roots)push(screen);while(pending.length>0){let body=folder.snippets.get(pending.pop());if(body)push(body)}return reached}function unusedSnippetIds(folder){let reachable=reachableSnippetIds(folder,folder.screens.values());return[...folder.snippets.keys()].filter((id)=>!reachable.has(id)).sort()}function snippetReferencers(folder,snippetId){let screenIds=[];for(let[id,screen]of folder.screens)if(snippetIdsIn(screen).has(snippetId))screenIds.push(id);let snippetIds=[];for(let[id,snippet]of folder.snippets){if(id===snippetId)continue;if(snippetIdsIn(snippet).has(snippetId))snippetIds.push(id)}return{screenIds,snippetIds}}function projectNameFor(root){let local=localDesignOf(root);if(local)return local.projectName;let home=homedir5(),dir=root;while(dir!==home){if(existsSync6(join20(dir,".git")))return basename4(dir);let parent=dirname12(dir);if(parent===dir)break;dir=parent}return basename4(root)}function snippetSummaries(f){let unused=new Set(unusedSnippetIds(f));return[...f.snippets.entries()].map(([id,snippet])=>({id,name:snippet.name,params:snippet.params,unused:unused.has(id)}))}function createDesignRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let ctx=ctxFor(),f=ctx.folder,libraries=f.config.libraries?Object.fromEntries(Object.entries(f.config.libraries).map(([id,lib])=>[id,{providerId:lib.id,version:lib.version}])):{};return c.json({snapshotVersion:ctx.defaultProvider.version,providerId:ctx.defaultProvider.id,libraries,defaultLibrary:f.config.defaultLibrary??null,folderName:projectNameFor(f.root),theme:{name:f.theme.name},defaultScreen:f.config.defaultScreen??null,defaultBoard:f.config.defaultBoard??null,viewportPresets:f.config.viewportPresets,screens:[...f.screens.entries()].map(([id,screen])=>({id,name:screen.name,library:screen.library??f.config.defaultLibrary??null})),boards:activeBoards(f).map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,group:board.group??null})),boardGroups:f.config.boardGroups??[],archivedBoards:orderedBoards(f).filter(([,board])=>isArchived(board)).map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,group:board.group??null,archivedAt:board.archivedAt??null})),snippets:snippetSummaries(f).map((entry)=>({...entry,library:f.snippets.get(entry.id)?.library??f.config.defaultLibrary??null})),extensionsCount:Object.keys(f.config.extensions??{}).length})}),r}function createConfigRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let ctx=ctxFor(),f=ctx.folder,cfg=f.config;return c.json({root:f.root,folderName:projectNameFor(f.root),schemaVersion:cfg.schemaVersion,toolVersion:cfg.toolVersion,folderId:cfg.folderId??null,defaultLibrary:cfg.defaultLibrary,libraries:Object.entries(cfg.libraries).map(([id,lib])=>{let provider=ctx.providers[id],channel=provider?styleChannelOf(provider,cfg.styling?.framework):void 0;return{id,providerId:lib.id,version:lib.version,source:lib.source,styleLabel:channel?.editorLabel??null}}),styling:cfg.styling?.framework??null,viewportPresets:cfg.viewportPresets,defaultBoard:cfg.defaultBoard??null,defaultScreen:cfg.defaultScreen??null,componentsAlias:cfg.codegen?.componentsAlias??null,feedback:{enabled:cfg.feedback?.enabled??!1,contactOk:cfg.feedback?.contactOk??!1},themes:["default",...[...f.themes.keys()].filter((n)=>n!=="default").sort()],extensionsCount:Object.keys(cfg.extensions??{}).length})}),r}function createScreenRouter(folder){let r=new Hono2;return r.get("/:id",(c)=>{let screen=folder().screens.get(c.req.param("id"));if(!screen)return c.json({error:"screen not found"},404);return c.json(screen)}),r}function createBoardRouter(folder){let r=new Hono2;return r.get("/:id",(c)=>{let board=folder().boards.get(c.req.param("id"));if(!board)return c.json({error:"board not found"},404);return c.json(board)}),r}function createSnippetsRouter(folder){let r=new Hono2;return r.get("/",(c)=>{let f=folder();return c.json({snippets:snippetSummaries(f)})}),r.get("/:snippetId/instances",(c)=>{let f=folder(),snippetId=c.req.param("snippetId");if(!f.snippets.has(snippetId))return c.json({error:"snippet not found"},404);return c.json({instances:findSnippetInstances(f,snippetId)})}),r.get("/:snippetId",(c)=>{let snippet=folder().snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);return c.json(snippet)}),r}async function loadManifestForCtx(ctx){let onDisk=join20(ctx.folder.root,".design","manifest.json");try{let raw=await readFile12(onDisk,"utf8");return JSON.parse(raw)}catch{return ctx.defaultProvider.loadManifest()}}function createComponentsRouter(ctxFor){let r=new Hono2;return r.get("/",async(c)=>{let ctx=ctxFor(),manifest=await loadManifestForCtx(ctx),css=ctx.folder.config.styling?.framework,channelsByLibrary={};for(let[id,provider]of Object.entries(ctx.providers))channelsByLibrary[id]=styleChannelOf(provider,css);let body={manifest,styleChannel:styleChannelOf(ctx.defaultProvider,css),channelsByLibrary};return c.json(body)}),r}import{realpathSync as realpathSync2}from"fs";import{dirname as dirname13,join as join21}from"path";function liveExtensions(extensions){let out={};for(let[id,ext]of Object.entries(extensions??{}))if(ext.render==="live")out[id]=ext;return out}var DEFAULT_APP="";function partitionByApp(live){let groups=new Map;for(let[id,ext]of Object.entries(live)){let key=ext.app??DEFAULT_APP,list=groups.get(key),entry={id,importPath:ext.importPath};if(list)list.push(entry);else groups.set(key,[entry])}return groups}function hostAppForExtension(config,app){if(!app)return config.hostApp;return config.hostApps?.[app]??null}function resolveLiveImportWarning(folderRoot,config,importPath,app){let hostApp=hostAppForExtension(config,app);if(hostApp===null)return`app "${app}" is not a key of config.hostApps (known: ${Object.keys(config.hostApps??{}).join(", ")||"none"}) \u2014 the live island can't `+"bundle until it names a registered app.";let hostRoot=hostAppRootFrom(folderRoot,hostApp),aliases=aliasPairs(hostApp);try{return resolveImport(importPath,hostRoot,aliases),null}catch(e){let fix=app?`Fix config.hostApps["${app}"].root/aliases or the importPath`:"Set config.hostApp.root/aliases or fix the importPath";return`live import "${importPath}" did not resolve from host root ${hostRoot} (${e instanceof Error?e.message:String(e)}). ${fix}; until it resolves the canvas shows the placeholder instead of the real component.`}}var LOADER_MERGE_BODY=`]);
|
|
159
|
+
`:code;return c.body(body,200,{"Content-Type":"text/javascript; charset=utf-8","Cache-Control":"no-store","Access-Control-Allow-Origin":"*"})}),r.get("/status",async(c)=>{let lib=c.req.query("lib")||defaultLibraryId(),refs=(c.req.query("refs")??"").split(",").filter(Boolean),result=await bundler.build(lib,refs);return c.json({usable:result.usable,diagnostics:result.diagnostics,errors:result.errors})}),r}import{readFileSync as readFileSync7}from"fs";import{join as join20}from"path";var CONTENT_TYPES={png:"image/png",json:"application/json",mhtml:"multipart/related",svg:"image/svg+xml",jpg:"image/jpeg",jpeg:"image/jpeg",webp:"image/webp",gif:"image/gif",css:"text/css"};function contentType(file){let ext=file.split(".").pop()?.toLowerCase()??"";return CONTENT_TYPES[ext]??"application/octet-stream"}function createCapturesRouter(folder){let r=new Hono2,rootOf=()=>{let f=folder(),folderId=f.config.folderId;return folderId?{root:f.root,folderId}:{root:f.root}};return r.get("/",(c)=>{let{root,folderId}=rootOf();return c.json({captures:listCaptures(root,folderId)})}),r.get("/:id",(c)=>{let id=c.req.param("id");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let{root,folderId}=rootOf(),manifest=readCaptureManifest(root,id,folderId);if(!manifest)return c.json({error:"capture not found"},404);return c.json(manifest)}),r.get("/:id/:file{.+}",(c)=>{let id=c.req.param("id"),rel=c.req.param("file");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let parts=rel.split("/");if(parts.length>2||!parts.every((p)=>isSafeCaptureFile(p)))return c.json({error:"bad capture file"},400);let{root,folderId}=rootOf();try{let body=readFileSync7(join20(captureDir(root,id,folderId),...parts));return c.body(new Uint8Array(body),200,{"Content-Type":contentType(parts[parts.length-1]??""),"Cache-Control":"no-store"})}catch{return c.json({error:"capture file not found"},404)}}),r.delete("/:id",(c)=>{let id=c.req.param("id");if(!isSafeCaptureId(id))return c.json({error:"bad capture id"},400);let{root,folderId}=rootOf();return deleteCapture(root,id,folderId)?c.json({ok:!0,id}):c.json({error:"capture not found"},404)}),r}var CreateBody=object({boardId:string().min(1),body:string().min(1).max(4000),anchor:CommentAnchorSchema.optional(),author:CommentAuthorSchema.optional(),scope:_enum(["local","shared"]).optional()}),ReplyBody=object({body:string().min(1).max(4000),author:CommentAuthorSchema.optional()}),UpdateBody=object({resolved:boolean().optional(),scope:literal("shared").optional()}).refine((value)=>value.resolved!==void 0||value.scope!==void 0);function createCommentsRouter(service){let r=new Hono2,respond=async(c,op)=>{try{return c.json(await op())}catch(error){if(error instanceof CommentStoreError)return c.json({error:{code:error.code,message:error.message}},error.code==="not-found"?404:400);if(error instanceof ZodError)return c.json({error:{code:"invalid",message:error.message}},400);throw error}};return r.get("/",(c)=>{let boardId=c.req.query("boardId")??"",statusParam=c.req.query("status")??"open",status=statusParam==="resolved"||statusParam==="all"?statusParam:"open",scopeParam=c.req.query("scope")??"all",scope=scopeParam==="local"||scopeParam==="shared"?scopeParam:"all";return respond(c,async()=>({threads:await service.list(boardId,status,scope)}))}),r.get("/cloud",(c)=>respond(c,()=>service.cloudAvailability(c.req.query("boardId")??""))),r.get("/:id",(c)=>respond(c,async()=>({thread:await service.get(c.req.param("id"))}))),r.post("/",async(c)=>{let parsed=CreateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>({thread:await service.create(parsed.data)}))}),r.post("/:id/messages",async(c)=>{let parsed=ReplyBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>({thread:await service.reply(c.req.param("id"),parsed.data)}))}),r.patch("/:id",async(c)=>{let parsed=UpdateBody.safeParse(await c.req.json().catch(()=>{return}));if(!parsed.success)return c.json({error:{code:"invalid",message:parsed.error.message}},400);return respond(c,async()=>{let thread=await service.get(c.req.param("id"));if(parsed.data.resolved!==void 0)thread=await service.setResolved(thread.id,parsed.data.resolved);if(parsed.data.scope==="shared"&&thread.scope==="local")thread=await service.promoteToShared(thread.id);return{thread}})}),r.delete("/:id/messages/:messageId",(c)=>respond(c,async()=>({thread:await service.deleteMessage(c.req.param("id"),c.req.param("messageId"))}))),r.delete("/:id",(c)=>respond(c,()=>service.delete(c.req.param("id")))),r}import{readFile as readFile12}from"fs/promises";import{join as join21}from"path";function findSnippetInstances(folder,snippetId){let out=[];function walk(node,path,screenIdLabel){if(isSnippetInstance(node)){if(node.$snippet===snippetId)out.push({screenId:screenIdLabel,path:path.join("."),hasOverride:typeof node.$extraClassName==="string"&&node.$extraClassName!=="",args:node.args??{}});return}if(!isComponentNode(node))return;if(!node.children)return;for(let i=0;i<node.children.length;i++){let child=node.children[i];if(child)walk(child,[...path,i],screenIdLabel)}}for(let[id,screen]of folder.screens)walk(screen.tree,[],id);for(let[id,snippet]of folder.snippets){if(id===snippetId)continue;walk(snippet.tree,[],`snippet:${id}`)}return out}function snippetIdsIn(value,out=new Set){if(value===null||typeof value!=="object")return out;if(Array.isArray(value)){for(let item of value)snippetIdsIn(item,out);return out}let record=value;if(typeof record.$snippet==="string")out.add(record.$snippet);for(let nested of Object.values(record))snippetIdsIn(nested,out);return out}function reachableSnippetIds(folder,roots){let reached=new Set,pending=[],push=(from)=>{for(let id of snippetIdsIn(from)){if(reached.has(id))continue;reached.add(id),pending.push(id)}};for(let screen of roots)push(screen);while(pending.length>0){let body=folder.snippets.get(pending.pop());if(body)push(body)}return reached}function unusedSnippetIds(folder){let reachable=reachableSnippetIds(folder,folder.screens.values());return[...folder.snippets.keys()].filter((id)=>!reachable.has(id)).sort()}function snippetReferencers(folder,snippetId){let screenIds=[];for(let[id,screen]of folder.screens)if(snippetIdsIn(screen).has(snippetId))screenIds.push(id);let snippetIds=[];for(let[id,snippet]of folder.snippets){if(id===snippetId)continue;if(snippetIdsIn(snippet).has(snippetId))snippetIds.push(id)}return{screenIds,snippetIds}}function snippetSummaries(f){let unused=new Set(unusedSnippetIds(f));return[...f.snippets.entries()].map(([id,snippet])=>({id,name:snippet.name,params:snippet.params,unused:unused.has(id)}))}function createDesignRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let ctx=ctxFor(),f=ctx.folder,libraries=f.config.libraries?Object.fromEntries(Object.entries(f.config.libraries).map(([id,lib])=>[id,{providerId:lib.id,version:lib.version}])):{};return c.json({snapshotVersion:ctx.defaultProvider.version,providerId:ctx.defaultProvider.id,libraries,defaultLibrary:f.config.defaultLibrary??null,designName:f.config.name,theme:{name:f.theme.name},defaultScreen:f.config.defaultScreen??null,defaultBoard:f.config.defaultBoard??null,viewportPresets:f.config.viewportPresets,screens:[...f.screens.entries()].map(([id,screen])=>({id,name:screen.name,library:screen.library??f.config.defaultLibrary??null})),boards:activeBoards(f).map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,group:board.group??null})),boardGroups:f.config.boardGroups??[],archivedBoards:orderedBoards(f).filter(([,board])=>isArchived(board)).map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,group:board.group??null,archivedAt:board.archivedAt??null})),snippets:snippetSummaries(f).map((entry)=>({...entry,library:f.snippets.get(entry.id)?.library??f.config.defaultLibrary??null})),extensionsCount:Object.keys(f.config.extensions??{}).length})}),r}function createConfigRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let ctx=ctxFor(),f=ctx.folder,cfg=f.config;return c.json({root:f.root,designName:cfg.name,schemaVersion:cfg.schemaVersion,toolVersion:cfg.toolVersion,folderId:cfg.folderId??null,defaultLibrary:cfg.defaultLibrary,libraries:Object.entries(cfg.libraries).map(([id,lib])=>{let provider=ctx.providers[id],channel=provider?styleChannelOf(provider,cfg.styling?.framework):void 0;return{id,providerId:lib.id,version:lib.version,source:lib.source,styleLabel:channel?.editorLabel??null}}),styling:cfg.styling?.framework??null,viewportPresets:cfg.viewportPresets,defaultBoard:cfg.defaultBoard??null,defaultScreen:cfg.defaultScreen??null,componentsAlias:cfg.codegen?.componentsAlias??null,feedback:{enabled:cfg.feedback?.enabled??!1,contactOk:cfg.feedback?.contactOk??!1},themes:["default",...[...f.themes.keys()].filter((n)=>n!=="default").sort()],extensionsCount:Object.keys(cfg.extensions??{}).length})}),r}function createScreenRouter(folder){let r=new Hono2;return r.get("/:id",(c)=>{let screen=folder().screens.get(c.req.param("id"));if(!screen)return c.json({error:"screen not found"},404);return c.json(screen)}),r}function createBoardRouter(folder){let r=new Hono2;return r.get("/:id",(c)=>{let board=folder().boards.get(c.req.param("id"));if(!board)return c.json({error:"board not found"},404);return c.json(board)}),r}function createSnippetsRouter(folder){let r=new Hono2;return r.get("/",(c)=>{let f=folder();return c.json({snippets:snippetSummaries(f)})}),r.get("/:snippetId/instances",(c)=>{let f=folder(),snippetId=c.req.param("snippetId");if(!f.snippets.has(snippetId))return c.json({error:"snippet not found"},404);return c.json({instances:findSnippetInstances(f,snippetId)})}),r.get("/:snippetId",(c)=>{let snippet=folder().snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);return c.json(snippet)}),r}async function loadManifestForCtx(ctx){let onDisk=join21(ctx.folder.root,".design","manifest.json");try{let raw=await readFile12(onDisk,"utf8");return JSON.parse(raw)}catch{return ctx.defaultProvider.loadManifest()}}function createComponentsRouter(ctxFor){let r=new Hono2;return r.get("/",async(c)=>{let ctx=ctxFor(),manifest=await loadManifestForCtx(ctx),css=ctx.folder.config.styling?.framework,channelsByLibrary={};for(let[id,provider]of Object.entries(ctx.providers))channelsByLibrary[id]=styleChannelOf(provider,css);let body={manifest,styleChannel:styleChannelOf(ctx.defaultProvider,css),channelsByLibrary};return c.json(body)}),r}import{realpathSync as realpathSync4}from"fs";import{dirname as dirname12,join as join22}from"path";function liveExtensions(extensions){let out={};for(let[id,ext]of Object.entries(extensions??{}))if(ext.render==="live")out[id]=ext;return out}var DEFAULT_APP="";function partitionByApp(live){let groups=new Map;for(let[id,ext]of Object.entries(live)){let key=ext.app??DEFAULT_APP,list=groups.get(key),entry={id,importPath:ext.importPath};if(list)list.push(entry);else groups.set(key,[entry])}return groups}function hostAppForExtension(config,app){if(!app)return config.hostApp;return config.hostApps?.[app]??null}function resolveLiveImportWarning(folderRoot,config,importPath,app){let hostApp=hostAppForExtension(config,app);if(hostApp===null)return`app "${app}" is not a key of config.hostApps (known: ${Object.keys(config.hostApps??{}).join(", ")||"none"}) \u2014 the live island can't `+"bundle until it names a registered app.";let hostRoot=hostAppRootFrom(folderRoot,hostApp),aliases=aliasPairs(hostApp);try{return resolveImport(importPath,hostRoot,aliases),null}catch(e){let fix=app?`Fix config.hostApps["${app}"].root/aliases or the importPath`:"Set config.hostApp.root/aliases or fix the importPath";return`live import "${importPath}" did not resolve from host root ${hostRoot} (${e instanceof Error?e.message:String(e)}). ${fix}; until it resolves the canvas shows the placeholder instead of the real component.`}}var LOADER_MERGE_BODY=`]);
|
|
160
160
|
export const components = {};
|
|
161
161
|
export const runtimes = {};
|
|
162
162
|
for (const g of groups) {
|
|
@@ -171,8 +171,8 @@ ${appKeys.map((k)=>` import(${JSON.stringify(`./bundle-app.js?app=${encodeURICo
|
|
|
171
171
|
${LOADER_MERGE_BODY}`}function buildInlineLoaderModule(bundles){return`const groups = await Promise.all([
|
|
172
172
|
${bundles.map((r)=>` import("data:text/javascript;base64,${Buffer.from(r.code,"utf8").toString("base64")}"),`).join(`
|
|
173
173
|
`)}
|
|
174
|
-
${LOADER_MERGE_BODY}`}function realPath(path){try{return realpathSync2(path)}catch{return path}}class LiveBundler{folderRoot;hostAppsFor;liveExtensionsFor;minify;inline;cached=null;cachedApps=null;buildPromise=null;_version=0;constructor(folderRoot,hostAppsFor,liveExtensionsFor,minify=!1,inline=!1){this.folderRoot=folderRoot;this.hostAppsFor=hostAppsFor;this.liveExtensionsFor=liveExtensionsFor;this.minify=minify;this.inline=inline}get version(){return this._version}hostSourceDirs(){let config=this.hostAppsFor(),dirs=new Set;for(let[app,entries]of partitionByApp(this.liveExtensionsFor())){let hostApp=hostAppForExtension(config,app||void 0);if(hostApp===null)continue;let hostRoot=hostAppRootFrom(this.folderRoot,hostApp),realRoot=realPath(hostRoot),nodeModules=join21(realRoot,"node_modules");for(let entry of entries)try{let file=realPath(resolveImport(entry.importPath,hostRoot,aliasPairs(hostApp)));if(file.startsWith(realRoot)&&!file.startsWith(nodeModules))dirs.add(dirname13(file))}catch{}}return Array.from(dirs)}invalidate(){this.cached=null,this.cachedApps=null,this.buildPromise=null,this._version++}async build(){if(this.cached)return this.cached;if(this.buildPromise)return this.buildPromise;return this.buildPromise=this.doBuild().then((result)=>(this.cached=result,this.buildPromise=null,result)).catch((err)=>{this.buildPromise=null;let result={code:EMPTY_MODULE,errors:[{message:err instanceof Error?err.message:String(err)}]};return this.cached=result,result}),this.buildPromise}async buildApp(app){return await this.build(),this.cachedApps?.get(app)??{code:EMPTY_MODULE,errors:[{message:`no live bundle for app "${app}"`}]}}async doBuild(){let groups=partitionByApp(this.liveExtensionsFor());if(groups.size===0)return this.cachedApps=new Map,{code:EMPTY_MODULE,errors:[]};let config=this.hostAppsFor(),apps=new Map;for(let[app,entries]of groups){let hostApp=hostAppForExtension(config,app||void 0);if(hostApp===null){apps.set(app,{code:EMPTY_MODULE,errors:entries.map((e)=>({importPath:e.importPath,message:`${e.id}: extension names app "${app}" but config.hostApps has no such key.`}))});continue}apps.set(app,await bundleComponents({hostRoot:hostAppRootFrom(this.folderRoot,hostApp),entries,aliases:aliasPairs(hostApp),minify:this.minify,cacheKey:`${this.folderRoot}\x00${app}`}))}this.cachedApps=apps;let errors=[...apps.values()].flatMap((r)=>r.errors);if(apps.size===1)return{code:[...apps.values()][0].code,errors};return{code:this.inline?buildInlineLoaderModule([...apps.values()]):buildLoaderModule([...apps.keys()],this._version),errors}}}var screenNotFound=(screenId)=>({kind:"ScreenNotFound",screenId}),boardNotFound=(boardId)=>({kind:"BoardNotFound",boardId}),frameNotFound=(boardId,frameId)=>({kind:"FrameNotFound",boardId,frameId}),boardGroupNotFound=(groupId)=>({kind:"BoardGroupNotFound",groupId}),unknownComponent2=(ref,suggestions,hint)=>({kind:"UnknownComponent",ref,suggestions,...hint!==void 0?{hint}:{}}),invalidPath=(reason,path,hint)=>({kind:"InvalidPath",reason,...path!==void 0?{path}:{},...hint!==void 0?{hint}:{}}),invalidMove=(reason)=>({kind:"InvalidMove",reason}),lastScreen=(screenId)=>({kind:"LastScreen",screenId}),screenIdConflict=(screenId)=>({kind:"ScreenIdConflict",screenId,hint:`Screen "${screenId}" already exists (a route-scan may have scaffolded it as a placeholder). Build into it instead with compose { screenId: "${screenId}", mode: "replace", jsx: \u2026 }. `+"To replace the screen resource itself, remove_screen then add_screen. To create a separate screen, omit `id` (add_screen auto-suffixes a unique one)."}),screenIdExhausted=(base)=>({kind:"ScreenIdExhausted",base}),boardIdConflict=(boardId)=>({kind:"BoardIdConflict",boardId}),boardIdExhausted=(base)=>({kind:"BoardIdExhausted",base}),frameIdConflict=(boardId,frameId)=>({kind:"FrameIdConflict",boardId,frameId}),badRequest=(message,issues)=>{let hint=scalarChildrenHint(issues);return{kind:"BadRequest",message,...issues!==void 0?{issues}:{},...hint!==void 0?{hint}:{}}};function scalarChildrenHint(issues){return hasScalarChildrenIssue(issues)?'Node-level `children` must be an array of nodes. For scalar text, use `props.children` (e.g. props: { children: "Save" }) \u2014 or wrap it: children: ["Save"].':void 0}function hasScalarChildrenIssue(issues){if(!Array.isArray(issues))return!1;return issues.some((issue)=>{if(typeof issue!=="object"||issue===null)return!1;let i=issue;if(i.code==="invalid_type"&&i.expected==="array"&&Array.isArray(i.path)&&i.path.at(-1)==="children")return!0;return Array.isArray(i.errors)&&i.errors.some((branch)=>hasScalarChildrenIssue(branch))})}var snippetNotFound=(snippetId)=>({kind:"SnippetNotFound",snippetId}),snippetParamMismatch=(snippetId,reason,details)=>({kind:"SnippetParamMismatch",snippetId,reason,...details!==void 0?{details}:{}}),snippetCycle=(snippetId,viaPath)=>({kind:"SnippetCycle",snippetId,viaPath}),snippetInUse=(snippetId,screenIds,snippetIds)=>({kind:"SnippetInUse",snippetId,screenIds,snippetIds}),snippetIdConflict=(snippetId)=>({kind:"SnippetIdConflict",snippetId}),idNotFound=(screenId,id,hint)=>({kind:"IdNotFound",screenId,id,...hint!==void 0?{hint}:{}}),idConflict=(screenId,id,paths)=>({kind:"IdConflict",screenId,id,paths}),annotationConflict=(screenId,locator,existingId)=>({kind:"AnnotationConflict",screenId,locator,existingId}),annotationNotFound=(screenId,annotationId)=>({kind:"AnnotationNotFound",screenId,annotationId}),canvasNoteNotFound=(noteId)=>({kind:"CanvasNoteNotFound",noteId}),extensionIdConflict=(extensionId)=>({kind:"ExtensionIdConflict",message:`Extension "${extensionId}" already exists. Use update_extension to patch it.`,extensionId}),extensionNotFound=(extensionId)=>({kind:"ExtensionNotFound",message:`Extension "${extensionId}" doesn't exist.`,extensionId}),extensionInUse=(extensionId,references)=>({kind:"ExtensionInUse",message:`Extension "${extensionId}" is referenced by ${references.length} node${references.length===1?"":"s"} \u2014 remove the usages first.`,extensionId,references});function levenshtein(a,b){let al=a.length,bl=b.length;if(al===0)return bl;if(bl===0)return al;let dp=Array(bl+1);for(let j=0;j<=bl;j++)dp[j]=j;for(let i=1;i<=al;i++){let prev=i-1;dp[0]=i;for(let j=1;j<=bl;j++){let tmp=dp[j]??0,cost=a[i-1]===b[j-1]?0:1;dp[j]=Math.min((dp[j]??0)+1,(dp[j-1]??0)+1,prev+cost),prev=tmp}}return dp[bl]??0}var normalizeRef=(s)=>s.toLowerCase().replace(/[^a-z0-9]/g,"");function nearestRefs(target,refs,n=3){let t=normalizeRef(target);return refs.map((r)=>({r,d:levenshtein(t,normalizeRef(r))})).sort((a,b)=>a.d-b.d).slice(0,n).map((x)=>x.r)}var SNIPPET_TREE_PREFIX="snippet:";function isSnippetTreeId(screenId){return screenId.startsWith(SNIPPET_TREE_PREFIX)}function snippetIdFromTreeId(screenId){return screenId.slice(SNIPPET_TREE_PREFIX.length)}function getScreen(ctx,screenId){if(isSnippetTreeId(screenId)){let snippetId=snippetIdFromTreeId(screenId),snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return err(snippetNotFound(snippetId));return ok({id:screenId,name:snippet.name,tree:snippet.tree})}let s=ctx.folder.screens.get(screenId);return s?ok(s):err(screenNotFound(screenId))}function getBoard(ctx,boardId){let b=ctx.folder.boards.get(boardId);return b?ok(b):err(boardNotFound(boardId))}function resolve8(root,locator,screenId){let path=resolveLocator(root,locator);if(path!==null)return ok(path);if(isIdLocator(locator)){let id=locator.slice(1);return err(idNotFound(screenId,id,id==="root"?'There is no node with id "root". The root node is the empty path []; tools like screenshot/compare_to_url treat the whole screen as the default when `path` is omitted.':void 0))}return err(invalidPath(`No node at path ${JSON.stringify(locator)}`,locator,'Numeric paths go stale after siblings are added, removed, or moved. Re-locate the node with find_nodes (it returns current paths + ids), or give it a stable @id via set_node_id and address it as "@id".'))}function subtreeHasId(node,id){if(!isComponentNode(node))return!1;if(node.$id===id)return!0;return(node.children??[]).some((c)=>subtreeHasId(c,id))}function findInstanceExposingId(ctx,root,id){let hit=null;function walk(node,path){if(hit)return;if(isSnippetInstance(node)){let def=ctx.folder.snippets.get(node.$snippet);if(def&&subtreeHasId(def.tree,id))hit={instanceLocator:node.$id?`@${node.$id}`:JSON.stringify(path),snippetId:node.$snippet};return}if(!isComponentNode(node))return;let kids=node.children??[];for(let i=0;i<kids.length&&!hit;i++){let child=kids[i];if(child)walk(child,[...path,i])}}return walk(root,[]),hit}function resolveWithSnippetHint(ctx,root,locator,screenId){let r=resolve8(root,locator,screenId);if(r.ok||r.error.kind!=="IdNotFound")return r;let found=findInstanceExposingId(ctx,root,r.error.id);if(!found)return r;let hint=`"${r.error.id}" isn't a top-level node, but a node with that id lives inside snippet instance ${found.instanceLocator} (snippet "${found.snippetId}"), which is opaque to @id addressing. To change its props for just this instance: update_snippet_instance { path: "${found.instanceLocator}", innerPath: "@${r.error.id}", propPatch: {\u2026} }. To change it across all instances, edit the snippet `+"body with update_snippet.";return err(idNotFound(screenId,r.error.id,hint))}function getNode(root,locator,screenId){let r=resolve8(root,locator,screenId);if(!r.ok)return r;let n=pathAt(root,r.value);return n?ok(n):err(invalidPath(`No node at path ${JSON.stringify(r.value)}`,r.value,"Numeric paths go stale after siblings change. Re-locate with find_nodes or address by stable @id."))}function getComponentNode(root,locator,screenId){let r=getNode(root,locator,screenId);if(!r.ok)return r;if(!isComponentNode(r.value)){let r2=resolve8(root,locator,screenId),path=r2.ok?r2.value:[];return err(invalidPath(`Node at ${JSON.stringify(path)} is not a component (got ${describe2(r.value)}). For snippet instances use update_snippet_instance.`,path))}return ok(r.value)}function describe2(node){if(isComponentNode(node))return`$ref=${node.$ref}`;if("$snippet"in node)return`$snippet=${node.$snippet}`;return`$param=${node.$param}`}function getExtensions(ctx){return ctx.folder.config.extensions??{}}function libraryIdForScreen(ctx,screen){let id=screen.library;if(id&&ctx.folder.config.libraries?.[id])return id;return ctx.folder.config.defaultLibrary}function providerForScreen2(ctx,screen){return providerForScreen(screen,ctx.providers,ctx.defaultProvider)}function registryForScreen2(ctx,screen){return registryForScreen(screen,ctx.providers,ctx.defaultProvider,getExtensions(ctx),ctx.folder.config.styling?.framework)}function renderPassForScreen2(ctx,screen,theme,dark=!1){return renderPassForScreen(screen,ctx.providers,ctx.defaultProvider,theme,dark)}function ensureKnownComponent(ctx,ref,screen=null){let provider=screen?providerForScreen2(ctx,screen):ctx.defaultProvider;if(ref in provider.registry)return ok(void 0);let extensions=getExtensions(ctx);if(ref in extensions)return ok(void 0);let snippetMatch=[...ctx.folder.snippets.keys()].find((s)=>normalizeRef(s)===normalizeRef(ref));if(snippetMatch)return err(unknownComponent2(ref,[],`"${ref}" is a snippet, not a library component. In MCP use compose with its PascalCase tag; lower-level callers must use a {"$snippet":"${snippetMatch}"} node. "$ref" is only for library components and registered extensions.`));let known=[...Object.keys(provider.registry),...Object.keys(extensions)];return err(unknownComponent2(ref,nearestRefs(ref,known)))}function getSnippet(ctx,snippetId){let s=ctx.folder.snippets?.get(snippetId);return s?ok(s):err(snippetNotFound(snippetId))}function playwrightMissingMessage(msg){return`screenshot: Playwright is not installed. Run \`${CHROMIUM_INSTALL_CMD}\`, then retry \u2014 no server restart needed. Underlying error: ${msg}`}function captureTimeoutMessage(err,op){if(!isCaptureTimeout(err))return null;let msg=err instanceof Error?err.message:"";if(!/screenshot|setContent/i.test(msg))return null;return`${op} timed out rendering the page (capped at 20s). This is almost always a one-off render `+"stall \u2014 a cold web-font fetch or a first heavy paint \u2014 so retry; it usually clears. If it "+"persists, the page has expensive CSS (a large repeating background, heavy blur/shadow) or is very tall: simplify the heavy styles, or capture a smaller region/viewport."}function browserErrorMessage(err){if(err instanceof Error&&err.name==="BrowserMissingError")return err.message;let msg=err instanceof Error?err.message:String(err);if(/Cannot find package 'playwright'|MODULE_NOT_FOUND|chromium/i.test(msg))return playwrightMissingMessage(msg.split(`
|
|
175
|
-
`)[0]??msg);return null}function defaultViewport(folder){let presets=folder.config.viewportPresets,pick=presets.find((p)=>p.name.toLowerCase().includes("desktop"))??presets[0]??{w:1440,h:900};return{w:pick.w,h:pick.h}}function makeLiveUrl(ctx,bundler){return()=>Object.keys(liveExtensions(ctx.folder.config.extensions)).length>0?`/api/live/bundle.js?v=${bundler.version}`:void 0}async function screenMount(ctx,canvasBundler,screen){if(!providerForScreen2(ctx,screen).canvasBundleSpec)return{kind:"none"};let refs=collectSerializedRefs(serializeTree(screen.tree,{snippets:ctx.folder.snippets}));if(refs.length===0)return{kind:"none"};let libraryId=libraryIdForScreen(ctx,screen),extensionIds=new Set(Object.keys(ctx.folder.config.extensions??{})),extensions=refs.filter((ref)=>extensionIds.has(ref));if(extensions.length>0)return{kind:"server",libraryId,reason:`it uses the extension${extensions.length===1?"":"s"} ${extensions.join(", ")}, which ${extensions.length===1?"has":"have"} no browser-canvas source`};let bundle=await canvasBundler.build(libraryId,refs);if(bundle.usable)return{kind:"mounted",libraryId,refs,bundle};let blocked=bundle.diagnostics.filter((entry)=>entry.status==="unavailable"),reason=blocked.length?`${blocked.map((entry)=>entry.id).join(", ")} ${blocked.length===1?"has":"have"} no source that compiles for the browser`:`the browser bundle failed to build: ${bundle.errors[0]?.message??"no components resolved"}`;return{kind:"server",libraryId,reason,bundle}}function blockingErrors(bundle){return(bundle?.diagnostics??[]).filter((entry)=>entry.status==="unavailable").map((entry)=>{let first=entry.errors?.[0]??entry.note??"no browser source";return`${entry.id}: ${first.length>300?`${first.slice(0,300)}\u2026`:first}`})}async function mountDiagnostics(ctx,canvasBundler,screen){let mount=await screenMount(ctx,canvasBundler,screen).catch(()=>{return});if(mount?.kind!=="server")return[];let errors=blockingErrors(mount.bundle);return[{severity:"warning",code:"render/server-fallback",path:[],message:`This screen renders server-side from Velloo's bundled components, not the app's own, because ${mount.reason}. `+"The browser mount is all-or-nothing, so every component on the screen falls back together \u2014 including ones component_status reports as exact."+(errors.length?` ${errors.join(" | ")}`:""),suggestion:"Fix what blocks the listed component (component_status { screen } has the full errors), or replace it; captures will then show the app's components."}]}function makeCanvasBundle(ctx,canvasBundler){return async(screen,theme,dark)=>{let mount=await screenMount(ctx,canvasBundler,screen);if(mount.kind!=="mounted")return;let provider=providerForScreen2(ctx,screen);return{url:`/api/canvas/bundle.js?v=${canvasBundler.version}&lib=${encodeURIComponent(mount.libraryId)}&refs=${encodeURIComponent(mount.refs.join(","))}`,themeOptions:provider.themeToNative?.(theme,dark)??null}}}async function renderForCapture(ctx,screen,opts){let canvasOpt=await opts.canvasBundle(screen,opts.theme,opts.dark),{html}=await renderScreen(screen,opts.theme,{viewport:opts.viewport,snapshotCss:opts.snapshotCss,registry:registryForScreen2(ctx,screen),renderPass:renderPassForScreen2(ctx,screen,opts.theme,opts.dark),snippets:ctx.folder.snippets,customCss:ctx.folder.customCss,baseHref:opts.assetOrigin,liveBundleUrl:opts.liveUrl(),dark:opts.dark,...canvasOpt?{canvasBundle:canvasOpt}:{}});return html}function regionNode(region,rects,scaleFactor,screen){let area=region.w*region.h,best=rects.map((r)=>({path:r.path,x:r.x*scaleFactor,y:r.y*scaleFactor,w:r.w*scaleFactor,h:r.h*scaleFactor})).filter((r)=>{let ix=Math.max(0,Math.min(r.x+r.w,region.x+region.w)-Math.max(r.x,region.x)),iy=Math.max(0,Math.min(r.y+r.h,region.y+region.h)-Math.max(r.y,region.y));return ix*iy>=area*0.5}).sort((a,b)=>a.w*a.h-b.w*b.h)[0];if(!best)return null;let path=best.path===""?[]:best.path.split(".").map(Number),node=pathAt(screen.tree,path);if(!node)return{path};let id=nodeId(node);return{path,...isComponentNode(node)?{ref:node.$ref}:{},...id!==void 0?{id}:{}}}function contentHeightFromRects(rects){let max=0;for(let r of rects)max=Math.max(max,r.y+r.h);return Math.round(max)}function framesShorterThan(ctx,screenId,contentHeight,viewportW){let out=[];for(let board of ctx.folder.boards.values()){if(isArchived(board))continue;for(let frame of board.frames)if(frame.screen===screenId&&frame.w===viewportW&&frame.h<contentHeight)out.push({board:board.id,frame:frame.id,...frame.label?{label:frame.label}:{},frameHeight:frame.h,overflowBy:contentHeight-frame.h})}return out}function createExportRouter(ctxFor,jit,bundler,canvasBundler){let r=new Hono2,pipeline=(c)=>{let ctx=ctxFor();return{folder:ctx.folder,providers:ctx.providers,defaultProvider:ctx.defaultProvider,snapshotCss:()=>jit.build(),assetOrigin:()=>{let host=c.req.header("host");return host?`http://${host}/`:void 0},liveBundleUrl:()=>Object.keys(liveExtensions(ctx.folder.config.extensions)).length>0?`/api/live/bundle.js?v=${bundler.version}`:void 0,canvasBundleFor:makeCanvasBundle(ctx,canvasBundler)}},parseTarget=(raw)=>{let m=/^(.+)\.(png|pdf|html)$/.exec(raw);return m?.[1]&&m[2]?{id:m[1],ext:m[2]}:null},parseOpts=(c)=>{let mode=c.req.query("mode")??"light";if(!["light","dark","compare"].includes(mode))return null;let scale=Math.min(3,Math.max(0.25,Number(c.req.query("scale"))||1)),theme=c.req.query("theme");return{mode,scale,...theme?{theme}:{}}},send=(c,body,ext,name,warnings=[])=>{let headers={"Content-Type":ext==="png"?"image/png":ext==="pdf"?"application/pdf":"text/html; charset=utf-8","Content-Disposition":`attachment; filename="${exportFilename(name,ext)}"`};if(warnings.length>0)headers["x-velloo-export-warnings"]=encodeURIComponent(JSON.stringify(warnings));return c.body(typeof body==="string"?body:new Uint8Array(body),200,headers)},failed=(c,err)=>{let message=err instanceof Error?err.message:String(err),missing=err instanceof Error&&err.name==="BrowserMissingError";return c.json({error:message},missing?503:500)};return r.get("/frame/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <frameId>.png|pdf|html"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark|compare"},400);let p=pipeline(c),found=findFrame(p.folder,target.id);if(!found)return c.json({error:`frame not found: ${target.id}`},404);if(opts.mode==="compare"&&target.ext!=="png")return c.json({error:'mode "compare" is PNG-only'},400);let screen=p.folder.screens.get(found.frame.screen),name=found.frame.label??screen?.name??target.id;try{if(target.ext==="png")return send(c,await exportFramePng(p,found.board,found.frame,opts),"png",name);if(target.ext==="pdf")return send(c,await exportFramePdf(p,found.board,found.frame,opts),"pdf",name);let out=await exportFrameHtml(p,found.board,found.frame,opts);return send(c,out.html,"html",name,out.warnings)}catch(err){return failed(c,err)}}),r.get("/board/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <boardId>.png|pdf|html"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark|compare"},400);if(opts.mode==="compare")return c.json({error:'mode "compare" is frame-only \u2014 export the board light or dark'},400);let p=pipeline(c),board=p.folder.boards.get(target.id);if(!board)return c.json({error:`board not found: ${target.id}`},404);try{if(target.ext==="png")return send(c,await exportBoardPng(p,board,opts),"png",board.name);if(target.ext==="pdf")return send(c,await exportBoardPdf(p,board,opts),"pdf",board.name);let out=await exportBoardHtml(p,board,opts);return send(c,out.html,"html",board.name,out.warnings)}catch(err){return failed(c,err)}}),r.get("/screen/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <screenId>.html"},400);if(target.ext!=="html")return c.json({error:"screen exports are HTML \u2014 export a frame for PNG/PDF"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark"},400);let p=pipeline(c),screen=p.folder.screens.get(target.id);if(!screen)return c.json({error:`screen not found: ${target.id}`},404);let preset=p.folder.config.viewportPresets[0]??{name:"default",w:1440,h:900},viewport={w:Number(c.req.query("w"))||preset.w,h:Number(c.req.query("h"))||preset.h};try{let out=await exportScreenHtml(p,screen,{dark:opts.mode==="dark",...opts.theme?{theme:opts.theme}:{},viewport});return send(c,out.html,"html",screen.name||screen.id,out.warnings)}catch(err){return failed(c,err)}}),r}import{randomUUID}from"crypto";import{rm as rm2}from"fs/promises";import{join as join22}from"path";function validateScreenIds(screenId,screen){let first=findDuplicateIds(screen.tree)[0];if(first)return err(idConflict(screenId,first.id,first.paths));return ok(void 0)}async function persistScreen(folder,screenId,screen,coalesceKey){let validated=ScreenSchema.parse(screen),prev=folder.screens.get(screenId)??null;return folder.history.push({kind:"screen",screenId,screen:prev,coalesceKey}),await writeJsonAtomic(join22(folder.root,"screens",`${screenId}.json`),validated),folder.screens.set(screenId,validated),validated}function commitScreen(folder,screenId,screen,coalesceKey){return DoAsync(async function*(){if(yield*$(validateScreenIds(screenId,screen)),isSnippetTreeId(screenId)){let snippetId=snippetIdFromTreeId(screenId),prev=folder.snippets.get(snippetId);if(!prev)return yield*$({ok:!1,error:snippetNotFound(snippetId)});let updated=await persistSnippet(folder,snippetId,{...prev,tree:screen.tree},coalesceKey);return{id:screenId,name:updated.name,tree:updated.tree}}return await persistScreen(folder,screenId,screen,coalesceKey)})}async function deletePersistedScreen(folder,screenId){let prev=folder.screens.get(screenId)??null;folder.history.push({kind:"screen",screenId,screen:prev}),await rm2(join22(folder.root,"screens",`${screenId}.json`),{force:!0}),await rm2(join22(folder.root,"screens",`${screenId}.annotations.json`),{force:!0}),folder.screens.delete(screenId),folder.annotations.delete(screenId)}async function persistBoard(folder,boardId,board){let validated=BoardSchema.parse(board),prev=folder.boards.get(boardId)??null;return folder.history.push({kind:"board",boardId,board:prev}),await writeJsonAtomic(join22(folder.root,"boards",`${boardId}.json`),validated),folder.boards.set(boardId,validated),validated}async function persistBoards(folder,writes){let validated=writes.map(({boardId,board})=>({boardId,board:BoardSchema.parse(board)}));folder.history.push({kind:"boards",boards:validated.map(({boardId})=>({boardId,board:folder.boards.get(boardId)??null}))});for(let{boardId,board}of validated)await writeJsonAtomic(join22(folder.root,"boards",`${boardId}.json`),board),folder.boards.set(boardId,board);return validated.map((v)=>v.board)}async function deletePersistedBoard(folder,boardId){let prev=folder.boards.get(boardId)??null;folder.history.push({kind:"board",boardId,board:prev}),await rm2(join22(folder.root,"boards",`${boardId}.json`),{force:!0}),await rm2(join22(folder.root,"boards",`${boardId}.notes.json`),{force:!0}),folder.boards.delete(boardId),folder.notes.delete(boardId)}async function persistTheme(folder,theme,coalesceKey){let validated=ThemeSchema.parse(theme);return folder.history.push({kind:"theme",themeName:"default",theme:folder.theme,...coalesceKey?{coalesceKey}:{}}),await writeJsonAtomic(join22(folder.root,"theme","default.json"),validated),folder.theme=validated,folder.themes.set("default",validated),validated}async function persistNamedTheme(folder,name,theme,coalesceKey){if(name==="default")return persistTheme(folder,theme,coalesceKey);let validated=ThemeSchema.parse({...theme,name});return folder.history.push({kind:"theme",themeName:name,theme:folder.themes.get(name)??null,...coalesceKey?{coalesceKey}:{}}),await writeJsonAtomic(join22(folder.root,"theme",`${name}.json`),validated),folder.themes.set(name,validated),validated}async function persistSnippet(folder,snippetId,snippet,coalesceKey){let validated=SnippetSchema.parse(snippet),prev=folder.snippets.get(snippetId)??null;return folder.history.push({kind:"snippet",snippetId,snippet:prev,coalesceKey}),await writeJsonAtomic(join22(folder.root,"snippets",`${snippetId}.json`),validated),folder.snippets.set(snippetId,validated),validated}async function deletePersistedSnippet(folder,snippetId){let prev=folder.snippets.get(snippetId);if(prev)folder.history.push({kind:"snippet",snippetId,snippet:prev});await rm2(join22(folder.root,"snippets",`${snippetId}.json`),{force:!0}),folder.snippets.delete(snippetId)}async function persistAnnotations(folder,screenId,annotations){let validated=annotations.map((a)=>AnnotationSchema.parse(a)),path=join22(folder.root,"screens",`${screenId}.annotations.json`);if(validated.length===0)await rm2(path,{force:!0});else await writeJsonAtomic(path,validated);return folder.annotations.set(screenId,validated),validated}async function persistConfig(folder,config){let validated=ConfigSchema.parse(config);return await writeJsonAtomic(join22(folder.root,".design","config.json"),validated),folder.config=validated,validated}async function persistCanvasNotes(folder,boardId,notes){let validated=notes.map((n)=>CanvasNoteSchema.parse(n)),path=join22(folder.root,"boards",`${boardId}.notes.json`);if(validated.length===0)await rm2(path,{force:!0});else await writeJsonAtomic(path,validated);return folder.notes.set(boardId,validated),validated}function newAnnotationId(){return`ann_${randomUUID().replace(/-/g,"").slice(0,8)}`}function sameTarget(treeRoot,a,b){let pa=resolveLocator(treeRoot,a),pb=resolveLocator(treeRoot,b);if(!pa||!pb)return!1;return JSON.stringify(pa)===JSON.stringify(pb)}async function addAnnotation(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId));yield*$(resolve8(screen.tree,args.target.locator,args.screenId));let existing=ctx.folder.annotations.get(args.screenId)??[];for(let a of existing)if(sameTarget(screen.tree,a.target.locator,args.target.locator))return yield*$(err(annotationConflict(args.screenId,args.target.locator,a.id)));let annotation={id:newAnnotationId(),target:args.target,position:args.position??"auto",body:args.body,...args.collapsed!==void 0?{collapsed:args.collapsed}:{},author:args.author??"user"},next=[...existing,annotation];return await persistAnnotations(ctx.folder,args.screenId,next),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{annotation}})}async function updateAnnotation(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.annotations.get(args.screenId);if(!existing)return yield*$(err(ctx.folder.screens.has(args.screenId)?annotationNotFound(args.screenId,args.annotationId):screenNotFound(args.screenId)));let idx=existing.findIndex((a)=>a.id===args.annotationId);if(idx===-1)return yield*$(err(annotationNotFound(args.screenId,args.annotationId)));let next={...existing[idx],...args.patch.body!==void 0?{body:args.patch.body}:{},...args.patch.position!==void 0?{position:args.patch.position}:{}};if(args.patch.collapsed===null)delete next.collapsed;else if(args.patch.collapsed!==void 0)next.collapsed=args.patch.collapsed;let updated=[...existing];return updated[idx]=next,await persistAnnotations(ctx.folder,args.screenId,updated),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{annotation:next}})}async function removeAnnotation(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.annotations.get(args.screenId);if(!existing)return yield*$(err(ctx.folder.screens.has(args.screenId)?annotationNotFound(args.screenId,args.annotationId):screenNotFound(args.screenId)));if(!existing.some((a)=>a.id===args.annotationId))return yield*$(err(annotationNotFound(args.screenId,args.annotationId)));let next=existing.filter((a)=>a.id!==args.annotationId);return await persistAnnotations(ctx.folder,args.screenId,next),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{removedId:args.annotationId}})}import{randomUUID as randomUUID2}from"crypto";function newNoteId(){return`note_${randomUUID2().replace(/-/g,"").slice(0,8)}`}var DEFAULT_NOTE_WIDTH=240;function checkAttachment(ctx,board,attachment){let frame=board.frames.find((f)=>f.id===attachment.frameId);if(!frame)return err(frameNotFound(board.id,attachment.frameId));if(frame.screen!==attachment.screenId)return err(invalidPath(`Frame "${attachment.frameId}" shows screen "${frame.screen}", not "${attachment.screenId}"`));let screen=getScreen(ctx,attachment.screenId);if(!screen.ok)return screen;let resolved=resolve8(screen.value.tree,attachment.locator,attachment.screenId);return resolved.ok?ok(void 0):resolved}async function addNote(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(args.attachment)yield*$(checkAttachment(ctx,board,args.attachment));else if(args.x===void 0||args.y===void 0)return yield*$(err(invalidPath("A note without an attachment needs x and y")));let note={id:newNoteId(),...args.x!==void 0?{x:args.x}:{},...args.y!==void 0?{y:args.y}:{},width:args.width??DEFAULT_NOTE_WIDTH,body:args.body,...args.attachment?{attachment:args.attachment}:{}},existing=ctx.folder.notes.get(args.boardId)??[];return await persistCanvasNotes(ctx.folder,args.boardId,[...existing,note]),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{note}})}async function updateNote(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.notes.get(args.boardId)??[],idx=existing.findIndex((n)=>n.id===args.noteId);if(idx===-1)return yield*$(err(canvasNoteNotFound(args.noteId)));let next={...existing[idx],...args.patch.x!==void 0?{x:args.patch.x}:{},...args.patch.y!==void 0?{y:args.patch.y}:{},...args.patch.width!==void 0?{width:args.patch.width}:{},...args.patch.body!==void 0?{body:args.patch.body}:{}},updated=[...existing];return updated[idx]=next,await persistCanvasNotes(ctx.folder,args.boardId,updated),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{note:next}})}async function removeNote(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.notes.get(args.boardId)??[];if(!existing.some((n)=>n.id===args.noteId))return yield*$(err(canvasNoteNotFound(args.noteId)));let next=existing.filter((n)=>n.id!==args.noteId);return await persistCanvasNotes(ctx.folder,args.boardId,next),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{removedId:args.noteId}})}function createLockMap(){let chains=new Map;return{run(key,fn){let next=(chains.get(key)??Promise.resolve()).then(fn,fn),tail=next.then(()=>{return},()=>{return});return chains.set(key,tail),tail.then(()=>{if(chains.get(key)===tail)chains.delete(key)}),next},get size(){return chains.size}}}function broadcastTreeChange(ctx,screenId){if(isSnippetTreeId(screenId))ctx.broadcast({type:"snippet-changed",snippetId:snippetIdFromTreeId(screenId)});else ctx.broadcast({type:"screen-changed",screenId})}var locks=createLockMap(),lockKey=(folder,kind,id)=>`${folder.root}\x00${kind}\x00${id}`;function withScreenLock(folder,screenId,fn){if(isSnippetTreeId(screenId))return locks.run(lockKey(folder,"snippet",snippetIdFromTreeId(screenId)),fn);return locks.run(lockKey(folder,"screen",screenId),fn)}function withSnippetLock(folder,snippetId,fn){return locks.run(lockKey(folder,"snippet",snippetId),fn)}function withConfigLock(folder,fn){return locks.run(lockKey(folder,"config","config"),fn)}function withBoardLock(folder,boardId,fn){return locks.run(lockKey(folder,"board",boardId),fn)}function withBoardLocks(folder,boardIds,fn){let[head,...rest]=[...new Set(boardIds)].sort();if(head===void 0)return fn();return withBoardLock(folder,head,()=>withBoardLocks(folder,rest,fn))}function addAnnotation2(ctx,args){return tracked(ctx,"add_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>addAnnotation(ctx,args)))}function updateAnnotation2(ctx,args){return tracked(ctx,"update_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateAnnotation(ctx,args)))}function removeAnnotation2(ctx,args){return tracked(ctx,"remove_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeAnnotation(ctx,args)))}function addNote2(ctx,args){return tracked(ctx,"add_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>addNote(ctx,args)))}function updateNote2(ctx,args){return tracked(ctx,"update_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateNote(ctx,args)))}function removeNote2(ctx,args){return tracked(ctx,"remove_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeNote(ctx,args)))}function slugify(s,fallback){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||fallback}var MAX_GROUP_NAME_LENGTH=40,GROUP_COLORS=["#8b5cf6","#0ea5e9","#f59e0b","#f43f5e","#10b981","#6366f1","#ec4899","#84cc16"];function nextGroupColor(existing){let used=new Set(existing.map((g)=>g.color));return GROUP_COLORS.find((c)=>!used.has(c))??GROUP_COLORS[existing.length%8]}function boardGroups(config){return config.boardGroups??[]}function nameTooLong(name){if(name.length<=MAX_GROUP_NAME_LENGTH)return null;return badRequest(`group name too long (${name.length} chars) \u2014 max ${MAX_GROUP_NAME_LENGTH} characters`)}function uniqueGroupId(groups,base){let id=base,attempt=2;while(groups.some((g)=>g.id===id))id=`${base}-${attempt++}`;return id}function resolveGroup(config,ref){let trimmed=ref.trim();if(trimmed.length===0)return err(badRequest("group name is empty"));let tooLong=nameTooLong(trimmed);if(tooLong)return err(tooLong);let groups=boardGroups(config),match=groups.find((g)=>g.id===trimmed)??groups.find((g)=>g.name.toLowerCase()===trimmed.toLowerCase());if(match)return ok({config,groupId:match.id});let group={id:uniqueGroupId(groups,slugify(trimmed,"group")),name:trimmed,color:nextGroupColor(groups)};return ok({config:{...config,boardGroups:[...groups,group]},groupId:group.id})}async function addBoardGroup(ctx,args){return DoAsync(async function*(){let name=args.name.trim();if(name.length===0)return yield*$(err(badRequest("group name is empty")));let tooLong=nameTooLong(name);if(tooLong)return yield*$(err(tooLong));let groups=boardGroups(ctx.folder.config),group={id:uniqueGroupId(groups,slugify(name,"group")),name,color:args.color??nextGroupColor(groups)};return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:[...groups,group]}),ctx.broadcast({type:"config-changed"}),{group}})}async function updateBoardGroup(ctx,args){return DoAsync(async function*(){let groups=boardGroups(ctx.folder.config),current=groups.find((g)=>g.id===args.groupId);if(!current)return yield*$(err(boardGroupNotFound(args.groupId)));let next={...current};if(args.patch.name!==void 0){let name=args.patch.name.trim();if(name.length===0)return yield*$(err(badRequest("group name is empty")));let tooLong=nameTooLong(name);if(tooLong)return yield*$(err(tooLong));next.name=name}if(args.patch.color!==void 0)if(args.patch.color===null)delete next.color;else next.color=args.patch.color;return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:groups.map((g)=>g.id===args.groupId?next:g)}),ctx.broadcast({type:"config-changed"}),{group:next}})}async function removeBoardGroup(ctx,args){return DoAsync(async function*(){let groups=boardGroups(ctx.folder.config);if(!groups.some((g)=>g.id===args.groupId))return yield*$(err(boardGroupNotFound(args.groupId)));let ungroupedBoardIds=[];for(let[boardId,board]of ctx.folder.boards){if(board.group!==args.groupId)continue;let{group:_group,...rest}=board;await persistBoard(ctx.folder,boardId,rest),ungroupedBoardIds.push(boardId)}let remaining=groups.filter((g)=>g.id!==args.groupId);await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:remaining.length>0?remaining:void 0});for(let boardId of ungroupedBoardIds)ctx.broadcast({type:"board-changed",boardId});return ctx.broadcast({type:"config-changed"}),{removedGroupId:args.groupId,ungroupedBoardIds}})}async function reorderBoardGroups(ctx,args){let groups=boardGroups(ctx.folder.config),seen=new Set,next=[];for(let id of args.order){let group=groups.find((g)=>g.id===id);if(group&&!seen.has(id))next.push(group),seen.add(id)}for(let group of groups)if(!seen.has(group.id))next.push(group);return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:next}),ctx.broadcast({type:"config-changed"}),ok({order:next.map((g)=>g.id)})}function addBoardGroup2(ctx,args){return tracked(ctx,"add_board_group",{},()=>addBoardGroup(ctx,args))}function updateBoardGroup2(ctx,args){return tracked(ctx,"update_board_group",{},()=>updateBoardGroup(ctx,args))}function removeBoardGroup2(ctx,args){return tracked(ctx,"remove_board_group",{},()=>removeBoardGroup(ctx,args))}function reorderBoardGroups2(ctx,args){return tracked(ctx,"reorder_board_groups",{},()=>reorderBoardGroups(ctx,args))}function screensPlacedOnlyOn(folder,boardId){let board=folder.boards.get(boardId);if(!board)return[];let elsewhere=new Set;for(let[otherId,other]of folder.boards){if(otherId===boardId)continue;for(let frame of other.frames)elsewhere.add(frame.screen)}let only=new Set;for(let frame of board.frames){if(elsewhere.has(frame.screen))continue;if(!folder.screens.has(frame.screen))continue;only.add(frame.screen)}return[...only]}function boardNameTooLong(name){if(name.length<=MAX_BOARD_NAME_LENGTH)return null;return badRequest(`board name too long (${name.length} chars) \u2014 max ${MAX_BOARD_NAME_LENGTH} characters`)}async function addBoard(ctx,args){return DoAsync(async function*(){let tooLong=boardNameTooLong(args.name);if(tooLong)return yield*$(err(tooLong));let baseId=args.id??slugify(args.name,"board");if(args.id!==void 0&&ctx.folder.boards.has(args.id))return yield*$(err(boardIdConflict(args.id)));let boardId=baseId,attempt=2;while(ctx.folder.boards.has(boardId))if(boardId=`${baseId}-${attempt++}`,attempt>100)return yield*$(err(boardIdExhausted(baseId)));let groupId;if(args.group!==void 0){let resolved=yield*$(resolveGroup(ctx.folder.config,args.group));if(groupId=resolved.groupId,resolved.config!==ctx.folder.config)await persistConfig(ctx.folder,resolved.config),ctx.broadcast({type:"config-changed"})}let board={id:boardId,name:args.name,...groupId?{group:groupId}:{},frames:[],groups:[]};return await persistBoard(ctx.folder,boardId,board),ctx.broadcast({type:"board-changed",boardId}),{boardId,board}})}async function updateBoard(ctx,args){return DoAsync(async function*(){if(args.patch.name!==void 0){let tooLong=boardNameTooLong(args.patch.name);if(tooLong)return yield*$(err(tooLong))}let next={...yield*$(getBoard(ctx,args.boardId))};if(args.patch.name!==void 0)next.name=args.patch.name;if(args.patch.theme!==void 0)if(args.patch.theme===null)delete next.theme;else next.theme=args.patch.theme;if(args.patch.group!==void 0)if(args.patch.group===null)delete next.group;else{let resolved=yield*$(resolveGroup(ctx.folder.config,args.patch.group));if(next.group=resolved.groupId,resolved.config!==ctx.folder.config)await persistConfig(ctx.folder,resolved.config),ctx.broadcast({type:"config-changed"})}if(args.patch.archived!==void 0)if(args.patch.archived)next.archivedAt=new Date().toISOString();else delete next.archivedAt;return await persistBoard(ctx.folder,args.boardId,next),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{board:next}})}async function reorderBoards(ctx,args){let seen=new Set,moved=[];for(let id of args.order)if(ctx.folder.boards.has(id)&&!seen.has(id))moved.push(id),seen.add(id);let current=orderedBoards(ctx.folder).map(([id])=>id),next=0,order=current.map((id)=>seen.has(id)?moved[next++]:id);for(let id of moved.slice(next))order.push(id);let nextConfig={...ctx.folder.config,boardOrder:order};return await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),ok({order})}async function removeBoard(ctx,args){return DoAsync(async function*(){yield*$(getBoard(ctx,args.boardId));let orphaned=screensPlacedOnlyOn(ctx.folder,args.boardId),strandedSnippets=snippetsStrandedByRemoving(ctx.folder,orphaned);await deletePersistedBoard(ctx.folder,args.boardId);for(let screenId of orphaned)await withScreenLock(ctx.folder,screenId,()=>deletePersistedScreen(ctx.folder,screenId)),ctx.broadcast({type:"screen-changed",screenId});for(let snippetId of strandedSnippets)await deletePersistedSnippet(ctx.folder,snippetId),ctx.broadcast({type:"snippet-changed",snippetId});let order=ctx.folder.config.boardOrder;if(order?.includes(args.boardId)){let pruned=order.filter((id)=>id!==args.boardId),nextConfig={...ctx.folder.config,boardOrder:pruned.length>0?pruned:void 0};await persistConfig(ctx.folder,nextConfig)}return ctx.broadcast({type:"board-changed",boardId:args.boardId}),{removedBoardId:args.boardId,removedScreenIds:orphaned,removedSnippetIds:strandedSnippets}})}function snippetsStrandedByRemoving(folder,screenIds){if(screenIds.length===0)return[];let going=new Set(screenIds),before=reachableSnippetIds(folder,folder.screens.values()),after=reachableSnippetIds(folder,[...folder.screens].filter(([id])=>!going.has(id)).map(([,screen])=>screen));return[...before].filter((id)=>!after.has(id)&&folder.snippets.has(id)).sort()}function addBoard2(ctx,args){return tracked(ctx,"add_board",(v)=>({boardId:v.boardId}),()=>addBoard(ctx,args))}function updateBoard2(ctx,args){return tracked(ctx,"update_board",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateBoard(ctx,args)))}function removeBoard2(ctx,args){return tracked(ctx,"remove_board",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeBoard(ctx,args)))}function reorderBoards2(ctx,args){return tracked(ctx,"reorder_boards",{},()=>reorderBoards(ctx,args))}async function commit(ctx,next){let saved=await persistConfig(ctx.folder,next);return ctx.broadcast({type:"config-changed"}),saved}async function updateViewportPresets(ctx,args){let parsed=ViewportPresetSchema.array().safeParse(args.presets);if(!parsed.success)return err(badRequest("Each viewport preset needs a name and positive w/h.",parsed.error.issues));let presets=parsed.data.map((p)=>({...p,name:p.name.trim()}));if(presets.length===0)return err(badRequest("A folder needs at least one viewport preset."));if(presets.some((p)=>p.name===""))return err(badRequest("A viewport preset needs a name."));let seen=new Set;for(let p of presets){let key=p.name.toLowerCase();if(seen.has(key))return err(badRequest(`Two viewport presets are both named "${p.name}".`));seen.add(key)}return await commit(ctx,{...ctx.folder.config,viewportPresets:presets}),ok({presets})}async function updateDefaults(ctx,args){let next={...ctx.folder.config};if(args.defaultBoard!==void 0)if(args.defaultBoard===null)delete next.defaultBoard;else{if(!ctx.folder.boards.has(args.defaultBoard))return err(boardNotFound(args.defaultBoard));next.defaultBoard=args.defaultBoard}if(args.defaultScreen!==void 0)if(args.defaultScreen===null)delete next.defaultScreen;else{if(!ctx.folder.screens.has(args.defaultScreen))return err(screenNotFound(args.defaultScreen));next.defaultScreen=args.defaultScreen}let saved=await commit(ctx,next);return ok({defaultBoard:saved.defaultBoard??null,defaultScreen:saved.defaultScreen??null})}async function updateCodegen(ctx,args){let next={...ctx.folder.config};if(args.componentsAlias!==void 0){let alias=args.componentsAlias?.trim()??null;if(alias==="")return err(badRequest("The components alias can't be empty \u2014 clear it to use the default."));let codegen={...next.codegen,...alias===null?{}:{componentsAlias:alias}};if(alias===null)delete codegen.componentsAlias;if(Object.keys(codegen).length===0)delete next.codegen;else next.codegen=codegen}let saved=await commit(ctx,next);return ok({componentsAlias:saved.codegen?.componentsAlias??null})}async function updateFeedback(ctx,args){let current=ctx.folder.config.feedback,contactOk=args.contactOk??await readFeedbackContactOk(),feedback={enabled:args.enabled??current?.enabled??!1,contactOk};if(args.contactOk!==void 0)await writeFeedbackContactOk(args.contactOk);if(await writeRepoFeedback(ctx.folder.root,feedback))return ctx.folder.config={...ctx.folder.config,feedback},ctx.broadcast({type:"config-changed"}),ok({enabled:feedback.enabled,contactOk:feedback.contactOk});let saved=await commit(ctx,{...ctx.folder.config,feedback:{enabled:feedback.enabled}});return ok({enabled:saved.feedback?.enabled??!1,contactOk:feedback.contactOk})}function updateViewportPresets2(ctx,args){return tracked(ctx,"update_viewport_presets",{},()=>withConfigLock(ctx.folder,()=>updateViewportPresets(ctx,args)))}function updateDefaults2(ctx,args){return tracked(ctx,"update_defaults",{},()=>withConfigLock(ctx.folder,()=>updateDefaults(ctx,args)))}function updateCodegen2(ctx,args){return tracked(ctx,"update_codegen",{},()=>withConfigLock(ctx.folder,()=>updateCodegen(ctx,args)))}function updateFeedback2(ctx,args){return tracked(ctx,"update_feedback",{},()=>withConfigLock(ctx.folder,()=>updateFeedback(ctx,args)))}function autoPosition(frames){if(frames.length===0)return{x:0,y:0};let maxRight=0;for(let f of frames)if(f.x+f.w>maxRight)maxRight=f.x+f.w;return{x:maxRight+80,y:0}}function genFrameId(board,screenId){let existing=new Set(board.frames.map((f)=>f.id)),i=1;while(existing.has(`${screenId}-${i}`))i++;return`${screenId}-${i}`}async function addFrame(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(yield*$(getScreen(ctx,args.screenId)),args.id!==void 0&&board.frames.some((f)=>f.id===args.id))return yield*$(err(frameIdConflict(args.boardId,args.id)));let id=args.id??genFrameId(board,args.screenId),pos=args.x!==void 0&&args.y!==void 0?{x:args.x,y:args.y}:autoPosition(board.frames),frame={id,screen:args.screenId,x:pos.x,y:pos.y,w:args.w,h:args.h,...args.label!==void 0?{label:args.label}:{},...args.group!==void 0?{group:args.group}:{}},nextBoard={...board,frames:[...board.frames,frame]};return await persistBoard(ctx.folder,args.boardId,nextBoard),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frame}})}async function moveFrame(ctx,args){return DoAsync(async function*(){let from=yield*$(getBoard(ctx,args.boardId)),to=yield*$(getBoard(ctx,args.toBoardId));if(args.boardId===args.toBoardId)return yield*$(err(invalidMove(`Frame "${args.frameId}" is already on board "${args.boardId}" \u2014 use update_frame to reposition it.`)));let frame=from.frames.find((f)=>f.id===args.frameId);if(!frame)return yield*$(err(frameNotFound(args.boardId,args.frameId)));let pos=args.x!==void 0&&args.y!==void 0?{x:args.x,y:args.y}:autoPosition(to.frames),taken=to.frames.some((f)=>f.id===frame.id),{group:_dropped,...carried}=frame,moved={...carried,id:taken?genFrameId(to,frame.screen):frame.id,x:pos.x,y:pos.y};return await persistBoards(ctx.folder,[{boardId:args.toBoardId,board:{...to,frames:[...to.frames,moved]}},{boardId:args.boardId,board:{...from,frames:from.frames.filter((f)=>f.id!==args.frameId)}}]),ctx.broadcast({type:"board-changed",boardId:args.toBoardId}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frame:moved,fromBoardId:args.boardId,toBoardId:args.toBoardId}})}async function removeFrame(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(board.frames.findIndex((f)=>f.id===args.frameId)===-1)return yield*$(err(frameNotFound(args.boardId,args.frameId)));let nextFrames=board.frames.filter((f)=>f.id!==args.frameId);return await persistBoard(ctx.folder,args.boardId,{...board,frames:nextFrames}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{removedFrameId:args.frameId}})}function applyPatch2(frame,patch){let next={...frame};if(patch.x!==void 0)next.x=patch.x;if(patch.y!==void 0)next.y=patch.y;if(patch.w!==void 0)next.w=patch.w;if(patch.h!==void 0)next.h=patch.h;if(patch.label!==void 0)if(patch.label===null)delete next.label;else next.label=patch.label;if(patch.group!==void 0)if(patch.group===null)delete next.group;else next.group=patch.group;if(patch.scheme!==void 0)if(patch.scheme===null)delete next.scheme;else next.scheme=patch.scheme;return next}async function updateFrames(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId)),nextFrames=[...board.frames];for(let{frameId,patch}of args.patches){let idx=nextFrames.findIndex((f)=>f.id===frameId);if(idx===-1)return yield*$(err(frameNotFound(args.boardId,frameId)));nextFrames[idx]=applyPatch2(nextFrames[idx],patch)}return await persistBoard(ctx.folder,args.boardId,{...board,frames:nextFrames}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frames:nextFrames}})}function addFrame2(ctx,args){return tracked(ctx,"add_frame",(v)=>({boardId:args.boardId,frameId:v.frame.id,screenId:v.frame.screen}),()=>withBoardLock(ctx.folder,args.boardId,()=>addFrame(ctx,args)))}function updateFrames2(ctx,args){return tracked(ctx,"update_frame",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateFrames(ctx,args)))}function removeFrame2(ctx,args){return tracked(ctx,"remove_frame",{boardId:args.boardId,frameId:args.frameId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeFrame(ctx,args)))}function moveFrame2(ctx,args){return tracked(ctx,"move_frame",(v)=>({boardId:v.toBoardId,frameId:v.frame.id,screenId:v.frame.screen}),()=>withBoardLocks(ctx.folder,[args.boardId,args.toBoardId],()=>moveFrame(ctx,args)))}var import_react4=__toESM(require_react_development(),1);function findNodeById(root,id){if(!isComponentNode(root))return;if(root.$id===id)return root;for(let child of root.children??[]){let hit=findNodeById(child,id);if(hit)return hit}return}function innerPathResolves(body,innerPath){if(innerPath.startsWith("@"))return findNodeById(body,innerPath.slice(1))!==void 0;let segments=innerPath===""?[]:innerPath.split(".").map(Number),cursor=body;for(let i of segments){if(!cursor||!isComponentNode(cursor)||!cursor.children)return!1;cursor=cursor.children[i]}return cursor!==void 0&&isComponentNode(cursor)}function resolveInnerNode(body,innerPath,snippetId){let target;if(innerPath.startsWith("@"))target=findNodeById(body,innerPath.slice(1));else{let segments=innerPath===""?[]:innerPath.split(".").map(Number);target=body;for(let i of segments){if(!target||!isComponentNode(target)||!target.children){target=void 0;break}target=target.children[i]}}if(!target||!isComponentNode(target))return err(invalidPath(`innerPath "${innerPath}" doesn't resolve to a component inside snippet "${snippetId}"`));return ok(target)}var SUBJECT_REF="__vellooInspectSubject",SUBJECT_TAG="velloo-inspect-subject";function Subject({children}){return import_react4.createElement(SUBJECT_TAG,null,children)}function virtualScreen(screen,tree){return{id:`${screen.id}__inspect`,name:`${screen.name} inspect`,library:screen.library,tree}}function renderIsolated(ctx,screen,node){let subScreen=virtualScreen(screen,node);return renderBodyGuarded(subScreen,registryForScreen2(ctx,subScreen),ctx.folder.snippets)}function markSubject(root,path){if(path.length===0)return{$ref:SUBJECT_REF,children:[root]};if(!isComponentNode(root))return null;let[index,...rest]=path,children=root.children;if(index===void 0||!children)return null;let child=children[index];if(!child)return null;let marked=markSubject(child,rest);if(!marked)return null;let next=[...children];return next[index]=marked,{...root,children:next}}function subjectMarkup(html){let open=`<${SUBJECT_TAG}>`,start=html.indexOf(open);if(start===-1)return null;let end=html.indexOf(`</${SUBJECT_TAG}>`,start);return end===-1?null:html.slice(start+open.length,end)}function renderInPlace(ctx,screen,root,path){let marked=markSubject(root,path);if(!marked)return null;let subScreen=virtualScreen(screen,marked),registry={...registryForScreen2(ctx,subScreen),[SUBJECT_REF]:Subject},{html}=renderBodyGuarded(subScreen,registry,ctx.folder.snippets);return subjectMarkup(html)}function inspectComponent(ctx,screen,node,site,note){let isolated=renderIsolated(ctx,screen,node),bodyHtml=isolated.failures.length>0?renderInPlace(ctx,screen,site.root,site.path)??isolated.html:isolated.html,className=node.props?.className??"",classes=typeof className==="string"?className.trim().split(/\s+/).filter(Boolean):[];return{ref:node.$ref,resolvedProps:{...node.props??{}},classes,bodyHtml,...note?{note}:{}}}async function inspect(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),node=yield*$(getNode(screen.tree,args.path,args.screenId));if(isSnippetInstance(node)){let snippet=ctx.folder.snippets.get(node.$snippet);if(!snippet)return yield*$(err(snippetNotFound(node.$snippet)));let innerPath=args.innerPath??"",body=resolveSnippetBody(node,snippet),inner=yield*$(resolveInnerNode(body,innerPath,node.$snippet)),note=innerPath===""?`Node is the body root of snippet instance "${node.$snippet}". Pass innerPath ("@id" or a dotted index like "0.2") to inspect a deeper node inside the body.`:`Node is inside snippet instance "${node.$snippet}" at innerPath "${innerPath}".`,innerSite=resolveLocator(body,innerPath===""?[]:innerPath)??[];return inspectComponent(ctx,screen,inner,{root:body,path:innerSite},note)}if(args.innerPath!==void 0&&args.innerPath!=="")return yield*$(err(invalidPath(`innerPath "${args.innerPath}" was given but the node at ${JSON.stringify(args.path)} is not a snippet instance`)));if(!isComponentNode(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(args.path)} is not inspectable (expected a component or snippet instance)`)));let site=yield*$(resolve8(screen.tree,args.path,args.screenId));return inspectComponent(ctx,screen,node,{root:screen.tree,path:site},void 0)})}function inspect2(ctx,args){return inspect(ctx,args)}function cloneNode(n){return JSON.parse(JSON.stringify(n))}function cloneScreen(s){return JSON.parse(JSON.stringify(s))}async function addScreen(ctx,args){return DoAsync(async function*(){let baseId=args.id??slugify(args.name,"screen");if(args.id!==void 0&&ctx.folder.screens.has(args.id))return yield*$(err(screenIdConflict(args.id)));let screenId=baseId,attempt=2;while(ctx.folder.screens.has(screenId))if(screenId=`${baseId}-${attempt++}`,attempt>100)return yield*$(err(screenIdExhausted(baseId)));let tree;if(args.fromScreenId!==void 0){let src=yield*$(getScreen(ctx,args.fromScreenId));tree=cloneScreen(src).tree}else if(args.tree!==void 0)tree=cloneNode(args.tree);else tree={$ref:"Card",props:{className:"p-6"}};let screen={id:screenId,name:args.name,tree};return await persistScreen(ctx.folder,screenId,screen),ctx.broadcast({type:"screen-changed",screenId}),{screenId,screen}})}async function removeScreen(ctx,args){return DoAsync(async function*(){if(yield*$(getScreen(ctx,args.screenId)),ctx.folder.screens.size<=1)return yield*$(err(lastScreen(args.screenId)));let removedFrames=[];for(let[boardId,board]of ctx.folder.boards){let referencing=board.frames.filter((f)=>f.screen===args.screenId);if(referencing.length===0)continue;let nextBoard={...board,frames:board.frames.filter((f)=>f.screen!==args.screenId)};await persistBoard(ctx.folder,boardId,nextBoard),ctx.broadcast({type:"board-changed",boardId}),removedFrames.push({boardId,frameIds:referencing.map((f)=>f.id)})}return await deletePersistedScreen(ctx.folder,args.screenId),ctx.broadcast({type:"screen-changed",screenId:args.screenId}),{removedScreenId:args.screenId,removedFrames}})}async function setScreenTree(ctx,args){let{screenId}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),prevRef="$ref"in screen.tree?screen.tree.$ref:"(root)",next=cloneScreen(screen);return next.tree=cloneNode(args.tree),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{screenId,replacedRootRef:prevRef}})}async function updateScreen(ctx,args){return DoAsync(async function*(){if(isSnippetTreeId(args.screenId))return yield*$(err(badRequest(`${args.screenId} is a snippet body, not a screen \u2014 rename it with update_snippet.`)));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen);if(args.patch.name!==void 0)next.name=args.patch.name;return await persistScreen(ctx.folder,args.screenId,next),ctx.broadcast({type:"screen-changed",screenId:args.screenId}),{screen:next}})}function addScreen2(ctx,args){return tracked(ctx,"add_screen",(v)=>({screenId:v.screenId}),()=>addScreen(ctx,args))}function removeScreen2(ctx,args){return tracked(ctx,"remove_screen",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeScreen(ctx,args)))}function updateScreen2(ctx,args){return tracked(ctx,"update_screen",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateScreen(ctx,args)))}function setScreenTree2(ctx,args){return tracked(ctx,"set_screen_tree",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>setScreenTree(ctx,args)))}function detectSnippetCycle(root,rootId,registry){let stack=[rootId],visited=new Set([rootId]);function walk(node){if(isSnippetInstance(node)){let id=node.$snippet;if(visited.has(id))return[...stack,id];let inner=registry.get(id);if(!inner)return null;visited.add(id),stack.push(id);let found=walk(inner.tree);if(stack.pop(),visited.delete(id),found)return found;return null}if(node.children)for(let child of node.children){let found=walk(child);if(found)return found}return null}return walk(root)}async function addSnippet(ctx,args){let id=args.id??slugify(args.name,"snippet");if(ctx.folder.snippets.has(id))return err(snippetIdConflict(id));let snippet={id,name:args.name,params:args.params??[],tree:args.tree},registry=new Map(ctx.folder.snippets);registry.set(id,snippet);let cycle=detectSnippetCycle(args.tree,id,registry);if(cycle)return err(snippetCycle(id,cycle));let persisted=await persistSnippet(ctx.folder,id,snippet);return ctx.broadcast({type:"snippet-changed",snippetId:id}),ok({snippetId:id,snippet:persisted})}async function instantiateSnippet(ctx,args){return DoAsync(async function*(){let snippet=yield*$(getSnippet(ctx,args.snippetId));yield*$(validateArgs(snippet,args.args??{})),yield*$(validateOverrides(snippet,args.overrides));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolvedParent=yield*$(resolve8(next.tree,args.parentPath,args.screenId)),parent=yield*$(getComponentNode(next.tree,resolvedParent,args.screenId));if(!parent.children)parent.children=[];let idx=args.index??parent.children.length;if(idx<0||idx>parent.children.length)return yield*$(err(invalidPath(`index ${idx} out of range for parent with ${parent.children.length} children`,resolvedParent)));let node={$snippet:snippet.id,...args.id!==void 0?{$id:args.id}:{},...args.extraClassName&&args.extraClassName.trim()!==""?{$extraClassName:args.extraClassName.trim()}:{},...args.args&&Object.keys(args.args).length>0?{args:args.args}:{},...args.overrides&&Object.keys(args.overrides).length>0?{$overrides:args.overrides}:{}};return parent.children.splice(idx,0,node),yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:[...resolvedParent,idx]}})}function validateArgs(snippet,passed){let declared=new Set(snippet.params.map((p)=>p.name)),{missing}=resolveSnippetArgs(snippet,passed),extras=Object.keys(passed).filter((k)=>!declared.has(k));if(missing.length===0&&extras.length===0)return{ok:!0,value:void 0};let parts=[];if(missing.length>0)parts.push(`missing required: ${missing.join(", ")}`);if(extras.length>0)parts.push(`unknown: ${extras.join(", ")}`);return{ok:!1,error:snippetParamMismatch(snippet.id,parts.join("; "),{missing,extras})}}function validateOverrides(snippet,overrides){if(!overrides)return{ok:!0,value:void 0};let bad=Object.keys(overrides).filter((key)=>!innerPathResolves(snippet.tree,key));if(bad.length===0)return{ok:!0,value:void 0};return{ok:!1,error:invalidPath(`overrides target nodes not found in snippet "${snippet.id}": ${bad.join(", ")} \u2014 `+'each key must be the "@id" of a body node, a dotted index path like "0.2", or "" for the root')}}async function removeSnippet(ctx,args){return DoAsync(async function*(){yield*$(getSnippet(ctx,args.snippetId));let{screenIds,snippetIds}=snippetReferencers(ctx.folder,args.snippetId);if(screenIds.length>0||snippetIds.length>0)return yield*$(err(snippetInUse(args.snippetId,screenIds,snippetIds)));return await deletePersistedSnippet(ctx.folder,args.snippetId),ctx.broadcast({type:"snippet-changed",snippetId:args.snippetId}),{removedId:args.snippetId}})}async function updateSnippet(ctx,args){return DoAsync(async function*(){let prev=yield*$(getSnippet(ctx,args.snippetId)),{innerPatch}=args.patch,tree=args.patch.tree??prev.tree;if(innerPatch!==void 0){if(!/^$|^\d+(\.\d+)*$|^@[a-zA-Z][a-zA-Z0-9_-]*$/.test(innerPatch.innerPath))return yield*$(err(invalidPath('innerPath must be a dotted index path like "0.2", an "@id" of a body node, or "" for the root')));if(!innerPathResolves(tree,innerPatch.innerPath))return yield*$(err(invalidPath(`innerPath "${innerPatch.innerPath}" doesn't resolve to a component inside snippet "${args.snippetId}"`)));tree=applySnippetOverrides(tree,{[innerPatch.innerPath]:{props:innerPatch.propPatch}})}let treeChanged=args.patch.tree!==void 0||innerPatch!==void 0,next={...prev,...args.patch.name!==void 0?{name:args.patch.name}:{},...args.patch.params!==void 0?{params:args.patch.params}:{},...treeChanged?{tree}:{}};if(args.patch.tree!==void 0){let registry=new Map(ctx.folder.snippets);registry.set(next.id,next);let cycle=detectSnippetCycle(next.tree,next.id,registry);if(cycle)return yield*$(err(snippetCycle(next.id,cycle)))}let persisted=await persistSnippet(ctx.folder,next.id,next);return ctx.broadcast({type:"snippet-changed",snippetId:next.id}),{snippet:persisted}})}async function updateSnippetArgs(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve8(next.tree,args.path,args.screenId)),node=yield*$(getNode(next.tree,resolved,args.screenId));if(!isSnippetInstance(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(resolved)} is not a snippet instance.`,resolved)));let merged={...node.args??{}};for(let[k,v]of Object.entries(args.argPatch))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete node.args;else node.args=merged;if(args.extraClassName!==void 0)if(args.extraClassName===null||args.extraClassName.trim()==="")delete node.$extraClassName;else node.$extraClassName=args.extraClassName.trim();return yield*$(await commitScreen(ctx.folder,args.screenId,next,args.gesture)),broadcastTreeChange(ctx,args.screenId),{path:resolved}})}function addSnippet2(ctx,args){return tracked(ctx,"add_snippet",(v)=>({snippetId:v.snippetId}),()=>withSnippetLock(ctx.folder,args.id??args.name,()=>addSnippet(ctx,args)))}function updateSnippet2(ctx,args){return tracked(ctx,"update_snippet",{snippetId:args.snippetId},()=>withSnippetLock(ctx.folder,args.snippetId,()=>updateSnippet(ctx,args)))}function removeSnippet2(ctx,args){return tracked(ctx,"remove_snippet",{snippetId:args.snippetId},()=>withSnippetLock(ctx.folder,args.snippetId,()=>removeSnippet(ctx,args)))}function instantiateSnippet2(ctx,args){return tracked(ctx,"instantiate_snippet",(v)=>({screenId:args.screenId,path:v.path,snippetId:args.snippetId}),()=>withScreenLock(ctx.folder,args.screenId,()=>instantiateSnippet(ctx,args)))}function updateSnippetArgs2(ctx,args){return tracked(ctx,"update_snippet_args",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateSnippetArgs(ctx,args)))}async function addNode(ctx,args){let{screenId,parentPath,componentRef}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId));yield*$(ensureKnownComponent(ctx,componentRef,screen));let next=cloneScreen(screen),resolvedParent=yield*$(resolve8(next.tree,parentPath,screenId)),parent=yield*$(getComponentNode(next.tree,resolvedParent,screenId));if(!parent.children)parent.children=[];let idx=args.index??parent.children.length;if(idx<0||idx>parent.children.length)return yield*$(err(invalidPath(`index ${idx} out of range for parent with ${parent.children.length} children`,resolvedParent)));let newNode={$ref:componentRef,...args.id!==void 0?{$id:args.id}:{},...args.props?{props:args.props}:{},...args.children?{children:args.children}:{},...args.emitAs?{$emitAs:args.emitAs}:{}};return parent.children.splice(idx,0,newNode),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{path:[...resolvedParent,idx]}})}function channelForScreen(ctx,screen){return styleChannelOf(providerForScreen2(ctx,screen),ctx.folder.config.styling?.framework)}function validateStylePayload(channel,style){if(style===null)return ok(null);let wantsString=channel.kind==="tailwind-classname";if(wantsString&&typeof style!=="string")return err(badRequest(`\`style\` on the "${channel.kind}" channel expects a className string (got ${typeof style}).`));if(!wantsString&&(typeof style!=="object"||Array.isArray(style)))return err(badRequest(`\`style\` on the "${channel.kind}" channel expects an object of ${channel.prop} properties (got ${Array.isArray(style)?"array":typeof style}).`));return ok(null)}function applyStyleToProps(channel,props,style){let prop=channel.prop;if(style===null)return delete props[prop],props;if(typeof style==="string"){let trimmed=style.trim();if(trimmed==="")delete props[prop];else props[prop]=trimmed;return props}let current=props[prop],merged=current&&typeof current==="object"&&!Array.isArray(current)?{...current}:{};for(let[k,v]of Object.entries(style))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete props[prop];else props[prop]=merged;return props}async function updateProps(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),channel=channelForScreen(ctx,screen);for(let{style}of args.patches)if(style!==void 0)yield*$(validateStylePayload(channel,style));let next=cloneScreen(screen),paths=[];for(let{path,propPatch,style}of args.patches){let resolved=yield*$(resolveWithSnippetHint(ctx,next.tree,path,args.screenId)),node=yield*$(getComponentNode(next.tree,resolved,args.screenId)),merged={...node.props??{}};for(let[k,v]of Object.entries(propPatch??{}))if(v===null)delete merged[k];else merged[k]=v;if(style!==void 0)applyStyleToProps(channel,merged,style);if(Object.keys(merged).length===0)delete node.props;else node.props=merged;paths.push(resolved)}return yield*$(await commitScreen(ctx.folder,args.screenId,next,args.gesture)),broadcastTreeChange(ctx,args.screenId),{paths}})}async function applyClasses(ctx,args){let className=args.classes.trim();return updateProps(ctx,{screenId:args.screenId,patches:[{path:args.path,propPatch:{className:className===""?null:className}}],gesture:args.gesture})}function isAncestor(ancestor,descendant){if(ancestor.length>=descendant.length)return!1;return ancestor.every((seg,i)=>descendant[i]===seg)}async function moveNode(ctx,args){let{screenId,fromPath,toParent}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),next=cloneScreen(screen),resolvedFrom=yield*$(resolve8(next.tree,fromPath,screenId)),resolvedTo=yield*$(resolve8(next.tree,toParent,screenId));if(resolvedFrom.length===0)return yield*$(err(invalidMove("Cannot move the screen root.")));if(isAncestor(resolvedFrom,resolvedTo)||JSON.stringify(resolvedFrom)===JSON.stringify(resolvedTo))return yield*$(err(invalidMove("Cannot move a node into itself or its descendant.")));let fromParentInfo=parentOf(resolvedFrom);if(!fromParentInfo)return yield*$(err(invalidPath("no parent",resolvedFrom)));let fromParent=pathAt(next.tree,fromParentInfo.parent);if(!fromParent||!isComponentNode(fromParent)||!fromParent.children)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolvedFrom)}`,resolvedFrom)));if(fromParentInfo.index>=fromParent.children.length)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolvedFrom)}`,resolvedFrom)));let[moved]=fromParent.children.splice(fromParentInfo.index,1);if(fromParent.children.length===0)delete fromParent.children;if(!moved)return yield*$(err(invalidPath("internal: detach lost node during move",resolvedFrom)));let adjustedToParent=resolvedTo,sameParent=JSON.stringify(fromParentInfo.parent)===JSON.stringify(resolvedTo),toIdx=args.toIndex??1/0;if(sameParent&&toIdx>fromParentInfo.index)toIdx=Math.max(0,toIdx-1);else if(resolvedTo.length>fromParentInfo.parent.length&&isAncestor(fromParentInfo.parent,resolvedTo)&&(resolvedTo[fromParentInfo.parent.length]??-1)>fromParentInfo.index)adjustedToParent=[...resolvedTo],adjustedToParent[fromParentInfo.parent.length]=adjustedToParent[fromParentInfo.parent.length]-1;let target=pathAt(next.tree,adjustedToParent);if(!target||!isComponentNode(target))return yield*$(err(invalidPath(`No parent at ${JSON.stringify(adjustedToParent)}`,adjustedToParent)));if(!target.children)target.children=[];if(toIdx=Math.min(toIdx,target.children.length),toIdx<0)toIdx=0;return target.children.splice(toIdx,0,moved),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{newPath:[...adjustedToParent,toIdx]}})}async function overrideSnippetProps(ctx,args){return DoAsync(async function*(){if(!/^$|^\d+(\.\d+)*$|^@[a-zA-Z][a-zA-Z0-9_-]*$/.test(args.innerPath))return yield*$(err(invalidPath('innerPath must be a dotted index path like "0.2", an "@id" of a node inside the body, or "" for the root')));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve8(next.tree,args.path,args.screenId)),node=pathAt(next.tree,resolved);if(!node||!isSnippetInstance(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(resolved)} is not a snippet instance`,resolved)));let instance=node,snippet=ctx.folder.snippets.get(instance.$snippet);if(!snippet)return yield*$(err(snippetNotFound(instance.$snippet)));if(!innerPathResolves(snippet.tree,args.innerPath))return yield*$(err(invalidPath(`innerPath "${args.innerPath}" doesn't resolve to a component inside snippet "${instance.$snippet}"`)));let overrides={...instance.$overrides??{}},merged={...overrides[args.innerPath]?.props??{}};for(let[k,v]of Object.entries(args.propPatch))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete overrides[args.innerPath];else overrides[args.innerPath]={props:merged};if(Object.keys(overrides).length===0)delete instance.$overrides;else instance.$overrides=overrides;return yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:resolved,innerPath:args.innerPath,overrides}})}async function removeNode(ctx,args){let{screenId,path}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),next=cloneScreen(screen),resolved=yield*$(resolve8(next.tree,path,screenId));if(resolved.length===0){if(!isComponentNode(next.tree))return yield*$(err(invalidPath("Cannot remove the screen root.",resolved)));let cleared=next.tree.children?.length??0;return delete next.tree.children,yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{removedRef:`(cleared ${cleared} child node${cleared===1?"":"s"} of the screen root \u2014 the root itself remains)`}}let parentInfo=parentOf(resolved);if(!parentInfo)return yield*$(err(invalidPath("no parent",resolved)));let parent=pathAt(next.tree,parentInfo.parent);if(!parent||!isComponentNode(parent)||!parent.children)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolved)}`,resolved)));if(parentInfo.index>=parent.children.length)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolved)}`,resolved)));let[removed]=parent.children.splice(parentInfo.index,1);if(parent.children.length===0)delete parent.children;return yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{removedRef:describeRemoved(removed)}})}function describeRemoved(node){if(!node)return"?";if(isComponentNode(node))return node.$ref;if("$snippet"in node)return`@${node.$snippet}`;return`$param:${node.$param}`}async function setNodeId(ctx,args){if(args.id!==null){let parsed=NodeIdSchema.safeParse(args.id);if(!parsed.success)return{ok:!1,error:badRequest(`Invalid node id ${JSON.stringify(args.id)}`,parsed.error.issues)}}return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve8(next.tree,args.path,args.screenId)),node=yield*$(getNode(next.tree,resolved,args.screenId));if(!isComponentNode(node)&&!isSnippetInstance(node))return yield*$({ok:!1,error:invalidPath(`Node at ${JSON.stringify(resolved)} cannot carry an id (param refs are anonymous).`,resolved)});if(args.id===null)delete node.$id;else node.$id=args.id;return yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:resolved,id:args.id}})}function addNode2(ctx,args){return tracked(ctx,"add_node",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>addNode(ctx,args)))}function updateProps2(ctx,args){return tracked(ctx,"update_props",(v)=>({screenId:args.screenId,...v.paths[0]?{path:v.paths[0]}:{}}),()=>withScreenLock(ctx.folder,args.screenId,()=>updateProps(ctx,args)))}function moveNode2(ctx,args){return tracked(ctx,"move_node",(v)=>({screenId:args.screenId,path:v.newPath}),()=>withScreenLock(ctx.folder,args.screenId,()=>moveNode(ctx,args)))}function removeNode2(ctx,args){return tracked(ctx,"remove_node",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeNode(ctx,args)))}function applyClasses2(ctx,args){return tracked(ctx,"apply_classes",(v)=>({screenId:args.screenId,...v.paths[0]?{path:v.paths[0]}:{}}),()=>withScreenLock(ctx.folder,args.screenId,()=>applyClasses(ctx,args)))}function setNodeId2(ctx,args){return tracked(ctx,"set_node_id",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>setNodeId(ctx,args)))}function overrideSnippetProps2(ctx,args){return tracked(ctx,"override_snippet_props",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>overrideSnippetProps(ctx,args)))}function findExtensionReferences(ctx,extensionId){let out=[];function visit(node,screenId,path){if(isComponentNode(node)){if(node.$ref===extensionId)out.push({screenId,path:path.join(".")});if(node.children)for(let i=0;i<node.children.length;i++){let child=node.children[i];if(child)visit(child,screenId,[...path,i])}}}for(let[screenId,screen]of ctx.folder.screens)visit(screen.tree,screenId,[]);for(let[snippetId,snippet]of ctx.folder.snippets)visit(snippet.tree,`snippet:${snippetId}`,[]);return out}function unknownHostApp(ctx,app){if(!app)return null;let known=Object.keys(ctx.folder.config.hostApps??{});if(known.includes(app))return null;return{kind:"BadRequest",message:`app "${app}" is not a key of config.hostApps.`,hint:known.length?`Known apps: ${known.join(", ")}. Omit \`app\` to use the default host app.`:"This folder has no config.hostApps \u2014 omit `app` to use the default host app."}}async function addExtension(ctx,args){let existing=ctx.folder.config.extensions??{};if(args.id in existing)return err(extensionIdConflict(args.id));let badApp=unknownHostApp(ctx,args.app);if(badApp)return err(badApp);for(let p of args.props){let parsed=ExtensionPropDescriptorSchema.safeParse(p);if(!parsed.success)return err({kind:"InvalidExtensionProp",message:`Invalid extension prop schema: ${parsed.error.issues[0]?.message??"unknown"}`,extensionId:args.id,prop:p.name})}let extension=ExtensionSchema.parse({importPath:args.importPath,category:args.category,description:args.description,props:args.props,origin:"agent",render:args.render,app:args.app,fit:args.fit}),shadowed=args.id in ctx.defaultProvider.registry?args.id:void 0,nextConfig={...ctx.folder.config,extensions:{...existing,[args.id]:extension}};await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"add_extension",{extension:args.id});let result={id:args.id,extension};if(shadowed)result.shadowedLibraryComponent=shadowed;if(extension.render==="live"){let warning=resolveLiveImportWarning(ctx.folder.root,ctx.folder.config,args.importPath,extension.app);if(warning)result.liveResolveWarning=warning}return ok(result)}async function updateExtension(ctx,args){let existing=ctx.folder.config.extensions??{},prev=existing[args.id];if(!prev)return err(extensionNotFound(args.id));let badApp=unknownHostApp(ctx,args.patch.app);if(badApp)return err(badApp);if(args.patch.props)for(let p of args.patch.props){let parsed=ExtensionPropDescriptorSchema.safeParse(p);if(!parsed.success)return err({kind:"InvalidExtensionProp",message:`Invalid extension prop schema: ${parsed.error.issues[0]?.message??"unknown"}`,extensionId:args.id,prop:p.name})}let next=ExtensionSchema.parse({...prev,...args.patch.importPath!==void 0?{importPath:args.patch.importPath}:{},...args.patch.props!==void 0?{props:args.patch.props}:{},...args.patch.category!==void 0?{category:args.patch.category}:{},...args.patch.description!==void 0?{description:args.patch.description}:{},...args.patch.render!==void 0?{render:args.patch.render}:{},...args.patch.app!==void 0?{app:args.patch.app}:{},...args.patch.fit!==void 0?{fit:args.patch.fit}:{}}),nextConfig={...ctx.folder.config,extensions:{...existing,[args.id]:next}};await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"update_extension",{extension:args.id});let result={id:args.id,extension:next};if(next.render==="live"){let warning=resolveLiveImportWarning(ctx.folder.root,ctx.folder.config,next.importPath,next.app);if(warning)result.liveResolveWarning=warning}return ok(result)}async function removeExtension(ctx,args){let existing=ctx.folder.config.extensions??{};if(!(args.id in existing))return err(extensionNotFound(args.id));let refs=findExtensionReferences(ctx,args.id);if(refs.length>0)return err(extensionInUse(args.id,refs));let{[args.id]:_,...rest}=existing,extensions=Object.keys(rest).length>0?rest:void 0,nextConfig={...ctx.folder.config,extensions};return await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"remove_extension",{extension:args.id}),ok({id:args.id})}function summarize(node,path){if(isComponentNode(node)){let props=node.props??{},children=props.children;return{path,kind:"component",ref:node.$ref,...nodeId(node)?{id:nodeId(node)}:{},...typeof props.className==="string"?{className:props.className}:{},...typeof children==="string"?{textPreview:children.slice(0,80)}:{},childCount:node.children?.length??0}}if(isSnippetInstance(node))return{path,kind:"snippet",ref:node.$snippet,...nodeId(node)?{id:nodeId(node)}:{},childCount:0};return{path,kind:"param",childCount:0}}async function findNodes(ctx,args){let screenR=getScreen(ctx,args.screenId);if(!screenR.ok)return screenR;let limit=args.limit??50,matches=[],total=0;function consider(node,path){let props=isComponentNode(node)?node.props??{}:{};if(args.ref!==void 0&&(!isComponentNode(node)||node.$ref!==args.ref))return;if(args.snippetId!==void 0&&(!isSnippetInstance(node)||node.$snippet!==args.snippetId))return;if(args.id!==void 0&&nodeId(node)!==args.id)return;if(args.classContains!==void 0){let cls=props.className;if(typeof cls!=="string"||!cls.includes(args.classContains))return}if(args.prop!==void 0){if(!(args.prop in props))return;if(args.propValue!==void 0&&props[args.prop]!==args.propValue)return}if(total+=1,matches.length<limit)matches.push(summarize(node,path))}function walk(node,path){if(consider(node,path),isComponentNode(node)&&node.children)node.children.forEach((child,i)=>{walk(child,[...path,i])})}return walk(screenR.value.tree,[]),ok({matches,total})}async function updateSnippetInstance(ctx,plan){return DoAsync(async function*(){let applied=[],path;if(plan.args)path=(yield*$(await updateSnippetArgs2(ctx,plan.args))).path,applied.push("args");if(plan.override)path=(yield*$(await overrideSnippetProps2(ctx,plan.override))).path,applied.push("override");return{path:path??[],applied}})}var invalidColor=(reason,hint)=>({kind:"InvalidColor",reason,...hint!==void 0?{hint}:{}}),invalidThemePath=(reason,hint)=>({kind:"InvalidThemePath",reason,...hint!==void 0?{hint}:{}}),unknownPreset=(presetName,hint)=>({kind:"UnknownPreset",presetName,...hint!==void 0?{hint}:{}}),themeBadRequest=(message,issues)=>({kind:"BadRequest",message,...issues!==void 0?{issues}:{}});var MUTATION_STATUS={ScreenNotFound:404,BoardNotFound:404,FrameNotFound:404,BoardGroupNotFound:404,SnippetNotFound:404,IdNotFound:404,AnnotationNotFound:404,CanvasNoteNotFound:404,ExtensionNotFound:404,UnknownComponent:422,SnippetParamMismatch:422,SnippetCycle:422,InvalidExtensionProp:422,InvalidPath:400,InvalidMove:400,ScreenIdConflict:400,ScreenIdExhausted:400,BoardIdConflict:400,BoardIdExhausted:400,FrameIdConflict:400,SnippetIdConflict:400,BadRequest:400,LastScreen:409,SnippetInUse:409,IdConflict:409,AnnotationConflict:409,ExtensionIdConflict:409,ExtensionInUse:409},THEME_STATUS={UnknownPreset:404,InvalidColor:400,InvalidThemePath:400,BadRequest:400,BulkTokensInvalid:400};function mutationToHttp(c,error){return c.json({error},MUTATION_STATUS[error.kind])}function themeToHttp(c,error){return c.json({error},THEME_STATUS[error.kind])}async function readBody(c){try{return await c.req.json()}catch{return}}function makeBoundRoute(b){return function(schema,handler){return async(c)=>{let body=await readBody(c),parsed=schema.safeParse(body);if(!parsed.success)return b.toHttp(c,b.badRequest("Request body failed validation.",parsed.error.issues));let result=await withActor({source:"canvas"},()=>handler(parsed.data,b.ctxFor()));return result.ok?c.json(result.value):b.toHttp(c,result.error)}}}function makeRoute(ctxFor){return makeBoundRoute({ctxFor,badRequest,toHttp:mutationToHttp})}function makeThemeRoute(ctxFor){return makeBoundRoute({ctxFor,badRequest:themeBadRequest,toHttp:themeToHttp})}function createAnnotationsRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.get("/:screenId",(c)=>{let ctx=ctxFor(),screenId=c.req.param("screenId"),screen=ctx.folder.screens.get(screenId),list=(ctx.folder.annotations.get(screenId)??[]).map((a)=>{let resolved=screen?resolveLocator(screen.tree,a.target.locator):null;return{...a,resolved}});return c.json({annotations:list})}),r.post("/add",route(AddAnnotationBody,(a,ctx)=>addAnnotation2(ctx,a))),r.post("/update",route(UpdateAnnotationBody,(a,ctx)=>updateAnnotation2(ctx,a))),r.post("/remove",route(RemoveAnnotationBody,(a,ctx)=>removeAnnotation2(ctx,a))),r}function createNotesRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.get("/:boardId",(c)=>{let ctx=ctxFor(),boardId=c.req.param("boardId"),list=(ctx.folder.notes.get(boardId)??[]).map((note)=>{if(!note.attachment)return note;let screen=ctx.folder.screens.get(note.attachment.screenId),resolved=screen?resolveLocator(screen.tree,note.attachment.locator):null;return{...note,resolved}});return c.json({notes:list})}),r.post("/add",route(AddNoteBody,(a,ctx)=>addNote2(ctx,a))),r.post("/update",route(UpdateNoteBody,(a,ctx)=>updateNote2(ctx,a))),r.post("/remove",route(RemoveNoteBody,(a,ctx)=>removeNote2(ctx,a))),r}function createMutateRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.post("/update_props",route(UpdatePropsBody,(a,ctx)=>updateProps2(ctx,a))),r.post("/apply_classes",route(ApplyClassesBody,(a,ctx)=>applyClasses2(ctx,a))),r.post("/set_node_id",route(SetNodeIdBody,(a,ctx)=>setNodeId2(ctx,a))),r.post("/add_board",route(AddBoardBody,(a,ctx)=>addBoard2(ctx,a))),r.post("/update_board",route(UpdateBoardBody,(a,ctx)=>updateBoard2(ctx,a))),r.post("/remove_board",route(RemoveBoardBody,(a,ctx)=>removeBoard2(ctx,a))),r.post("/reorder_boards",route(ReorderBoardsBody,(a,ctx)=>reorderBoards2(ctx,a))),r.post("/add_board_group",route(AddBoardGroupBody,(a,ctx)=>addBoardGroup2(ctx,a))),r.post("/update_board_group",route(UpdateBoardGroupBody,(a,ctx)=>updateBoardGroup2(ctx,a))),r.post("/remove_board_group",route(RemoveBoardGroupBody,(a,ctx)=>removeBoardGroup2(ctx,a))),r.post("/reorder_board_groups",route(ReorderBoardGroupsBody,(a,ctx)=>reorderBoardGroups2(ctx,a))),r.post("/update_viewport_presets",route(UpdateViewportPresetsBody,(a,ctx)=>updateViewportPresets2(ctx,a))),r.post("/update_defaults",route(UpdateDefaultsBody,(a,ctx)=>updateDefaults2(ctx,a))),r.post("/update_codegen",route(UpdateCodegenBody,(a,ctx)=>updateCodegen2(ctx,a))),r.post("/update_feedback",route(UpdateFeedbackBody,(a,ctx)=>updateFeedback2(ctx,a))),r.post("/add_frame",route(AddFrameBody,(a,ctx)=>addFrame2(ctx,a))),r.post("/update_frame",route(UpdateFrameBody,(a,ctx)=>updateFrames2(ctx,a))),r.post("/remove_frame",route(RemoveFrameBody,(a,ctx)=>removeFrame2(ctx,a))),r.post("/move_frame",route(MoveFrameBody,(a,ctx)=>moveFrame2(ctx,a))),r.post("/update_snippet",route(UpdateSnippetBody,(a,ctx)=>updateSnippet2(ctx,a))),r.post("/update_snippet_args",route(UpdateSnippetArgsBody,(a,ctx)=>updateSnippetArgs2(ctx,a))),r}function createPreflightRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let folder=ctxFor().folder,source={folder,providers:ctxFor().providers,defaultProvider:ctxFor().defaultProvider},kind=c.req.query("kind"),id=c.req.query("id");if(kind!==void 0||id!==void 0){if(kind!=="frame"&&kind!=="board"&&kind!=="screen")return c.json({error:"kind must be frame, board, or screen"},400);if(!id)return c.json({error:"id is required with kind"},400);let screens=screensForExportTarget(folder,kind,id);if(screens.length===0)return c.json({error:`no ${kind} with id "${id}"`},404);return c.json({failures:preflightScreens(source,screens)})}let boards=c.req.query("boards"),screens=boards===void 0||boards===""?[...folder.screens.values()]:screensForBoards(folder,boards.split(","));return c.json({failures:preflightScreens(source,screens)})}),r}function createPublishRouter(runner){let app=new Hono2,noPublisher="this server cannot publish \u2014 run the canvas through the velloo CLI",unavailable={error:"this server cannot publish \u2014 run the canvas through the velloo CLI"};app.get("/status",(c)=>{if(!runner)return c.json({state:"unavailable"});return c.json(runner.state())}),app.get("/targets",async(c)=>{let closed=(access)=>c.json({ready:!1,access,teams:[],slots:[]});if(!runner)return closed("signed-out");let access=await runner.access();if(access.state!=="ready")return closed(access.state);let[teams,destinationResult]=await Promise.all([runner.teams().catch(()=>[]),runner.destinations().then((value)=>({value})).catch((error)=>({error:error instanceof Error?error.message:String(error),signInRequired:asSignInRequired(error)}))]);if("error"in destinationResult){if(destinationResult.signInRequired)return closed(destinationResult.signInRequired);return c.json({ready:!0,access:"ready",teams,slots:[],destinationError:destinationResult.error})}return c.json({ready:!0,access:"ready",teams,...destinationResult.value})}),app.post("/",async(c)=>{if(!runner)return c.json(unavailable,503);let body=await c.req.json().catch(()=>({})),boardIds=Array.isArray(body.boardIds)?body.boardIds.filter((id)=>typeof id==="string"):[],visibility=body.visibility==="private"?"private":"public",title=typeof body.title==="string"?body.title:void 0,teamId=typeof body.teamId==="string"&&body.teamId?body.teamId:void 0;if(typeof body.password==="string"&&body.password.length<3)return c.json({error:"password must be at least 3 characters"},400);let password=typeof body.password==="string"?body.password:void 0,rawDestination=body.destination,destination=rawDestination?.mode==="new"?{mode:"new"}:rawDestination?.mode==="update"&&typeof rawDestination.slug==="string"&&(typeof rawDestination.expectedVersionId==="string"||rawDestination.expectedVersionId===null)?{mode:"update",slug:rawDestination.slug,expectedVersionId:rawDestination.expectedVersionId}:null;if(!destination)return c.json({error:"choose a publish destination"},400);let started=runner.start({boardIds,visibility,destination,...title?{title}:{},...password?{password}:{},...teamId?{teamId}:{}});if(!started)return c.json({error:"a publish is already running for this folder"},409);return c.json(started,202)});let cloudFail=(c,error)=>{let message=error instanceof Error?error.message:String(error),kind=asSignInRequired(error)?"LoggedOut":"HttpFailure";return c.json({error:{kind,message}},502)},noPublisherFail=(c)=>c.json({error:{kind:"HttpFailure",message:"this server cannot publish \u2014 run the canvas through the velloo CLI"}},503);return app.get("/published",async(c)=>{if(!runner)return noPublisherFail(c);try{return c.json({boards:await runner.published()})}catch(error){return cloudFail(c,error)}}),app.delete("/published/:slug",async(c)=>{if(!runner)return noPublisherFail(c);try{return await runner.unpublish(c.req.param("slug")),c.json({ok:!0})}catch(error){return cloudFail(c,error)}}),app.post("/reset",(c)=>(runner?.reset(),c.json({ok:!0}))),app}import{randomBytes}from"crypto";var SHOWCASES={Button:()=>({$ref:"Button",props:{children:"Button"}}),Badge:()=>({$ref:"Badge",props:{children:"Badge"}}),Heading:()=>({$ref:"Heading",props:{level:2,children:"Heading"}}),Text:()=>({$ref:"Text",props:{children:"Body text"}}),Label:()=>({$ref:"Card",props:{className:"flex flex-row items-center gap-2 ring-0 shadow-none bg-transparent p-0"},children:[{$ref:"Checkbox",props:{id:"showcase-label-cb",defaultChecked:!0}},{$ref:"Label",props:{htmlFor:"showcase-label-cb",children:"Accept terms"}}]}),Input:()=>({$ref:"Input",props:{placeholder:"you@company.com"}}),Textarea:()=>({$ref:"Textarea",props:{placeholder:"Write a message\u2026"}}),Switch:()=>({$ref:"Switch",props:{defaultChecked:!0}}),Checkbox:()=>({$ref:"Checkbox",props:{defaultChecked:!0}}),Slider:()=>({$ref:"Slider",props:{defaultValue:[40],className:"w-40"}}),Progress:()=>({$ref:"Progress",props:{value:66,className:"w-48"}}),Skeleton:()=>({$ref:"Skeleton",props:{className:"h-6 w-40"}}),Separator:()=>({$ref:"Separator",props:{className:"w-40"}}),Toggle:()=>({$ref:"Toggle",props:{variant:"outline",defaultPressed:!0,children:"Bold"}}),Calendar:()=>({$ref:"Calendar"}),Icon:()=>({$ref:"Icon",props:{name:"Sparkles",size:24}}),Placeholder:()=>({$ref:"Placeholder",props:{kind:"image",aspect:"16/9"}}),Divider:()=>({$ref:"Divider",props:{label:"or"}}),Avatar:()=>({$ref:"Avatar",children:[{$ref:"AvatarFallback",props:{children:"AB"}}]}),Alert:()=>({$ref:"Alert",children:[{$ref:"AlertTitle",props:{children:"Heads up!"}},{$ref:"AlertDescription",props:{children:"You can drop this component on a board."}}]}),Card:()=>({$ref:"Card",props:{className:"p-4 flex flex-col gap-1"},children:[{$ref:"Text",props:{className:"text-sm font-medium",children:"Card title"}},{$ref:"Text",props:{className:"text-xs text-muted-foreground",children:"A flexible container for content."}}]}),Tabs:()=>({$ref:"Tabs",props:{defaultValue:"overview"},children:[{$ref:"TabsList",children:[{$ref:"TabsTrigger",props:{value:"overview",children:"Overview"}},{$ref:"TabsTrigger",props:{value:"activity",children:"Activity"}},{$ref:"TabsTrigger",props:{value:"settings",children:"Settings"}}]}]}),Breadcrumb:()=>({$ref:"Breadcrumb",children:[{$ref:"BreadcrumbList",children:[{$ref:"BreadcrumbItem",children:[{$ref:"BreadcrumbLink",props:{children:"Home"}}]},{$ref:"BreadcrumbSeparator"},{$ref:"BreadcrumbItem",children:[{$ref:"BreadcrumbPage",props:{children:"Library"}}]}]}]}),Pagination:()=>({$ref:"Pagination",children:[{$ref:"PaginationContent",children:[{$ref:"PaginationPrevious"},{$ref:"PaginationItem",children:[{$ref:"PaginationLink",props:{children:"1"}}]},{$ref:"PaginationItem",children:[{$ref:"PaginationLink",props:{isActive:!0,children:"2"}}]},{$ref:"PaginationNext"}]}]}),Accordion:()=>({$ref:"Accordion",props:{type:"single",collapsible:!0,defaultValue:"a"},children:[{$ref:"AccordionItem",props:{value:"a"},children:[{$ref:"AccordionTrigger",props:{children:"What is Velloo?"}},{$ref:"AccordionContent",props:{children:"A local design canvas for solo devs."}}]}]}),RadioGroup:()=>({$ref:"RadioGroup",props:{defaultValue:"a"},children:[{$ref:"Card",props:{className:"flex items-center gap-2 ring-0 shadow-none p-0 bg-transparent"},children:[{$ref:"RadioGroupItem",props:{value:"a",id:"showcase-r1"}},{$ref:"Label",props:{htmlFor:"showcase-r1",children:"Option"}}]}]}),ToggleGroup:()=>({$ref:"ToggleGroup",props:{type:"single",defaultValue:"left"},children:[{$ref:"ToggleGroupItem",props:{value:"left",children:"Left"}},{$ref:"ToggleGroupItem",props:{value:"center",children:"Center"}},{$ref:"ToggleGroupItem",props:{value:"right",children:"Right"}}]}),Select:()=>({$ref:"Select",children:[{$ref:"SelectTrigger",props:{className:"w-40"},children:[{$ref:"SelectValue",props:{placeholder:"Choose\u2026"}}]}]}),Toaster:()=>({$ref:"Toaster",props:{position:"bottom-right"}}),Dialog:()=>({$ref:"Dialog",children:[{$ref:"DialogContent",props:{className:"w-72"},children:[{$ref:"DialogHeader",children:[{$ref:"DialogTitle",props:{children:"Confirm change"}},{$ref:"DialogDescription",props:{children:"Modal dialog content shown when triggered."}}]},{$ref:"DialogFooter",children:[{$ref:"Button",props:{variant:"outline",size:"sm",children:"Cancel"}},{$ref:"Button",props:{size:"sm",children:"Save"}}]}]}]}),AlertDialog:()=>({$ref:"AlertDialog",children:[{$ref:"AlertDialogContent",props:{className:"w-72"},children:[{$ref:"AlertDialogHeader",children:[{$ref:"AlertDialogTitle",props:{children:"Delete file?"}},{$ref:"AlertDialogDescription",props:{children:"This action can't be undone."}}]},{$ref:"AlertDialogFooter",children:[{$ref:"AlertDialogCancel",props:{size:"sm",children:"Cancel"}},{$ref:"AlertDialogAction",props:{variant:"destructive",size:"sm",children:"Delete"}}]}]}]}),Popover:()=>({$ref:"Popover",children:[{$ref:"PopoverContent",props:{className:"w-56"},children:[{$ref:"PopoverHeader",children:[{$ref:"PopoverTitle",props:{children:"Popover content"}},{$ref:"PopoverDescription",props:{children:"Anchored to a trigger."}}]}]}]}),DropdownMenu:()=>({$ref:"DropdownMenu",children:[{$ref:"DropdownMenuContent",props:{className:"w-44"},children:[{$ref:"DropdownMenuItem",props:{children:"Profile"}},{$ref:"DropdownMenuItem",props:{children:"Settings"}},{$ref:"DropdownMenuSeparator"},{$ref:"DropdownMenuItem",props:{variant:"destructive",children:"Log out"}}]}]}),Tooltip:()=>({$ref:"Tooltip",children:[{$ref:"TooltipContent",props:{children:"Helpful hint"}}]}),Sheet:()=>({$ref:"Sheet",children:[{$ref:"SheetContent",props:{side:"right",className:"h-56 w-64 rounded-lg p-4"},children:[{$ref:"SheetHeader",children:[{$ref:"SheetTitle",props:{children:"Slide-in panel"}},{$ref:"SheetDescription",props:{children:"Drawer mounted on a screen edge."}}]}]}]}),Collapsible:()=>({$ref:"Collapsible",props:{defaultOpen:!0},children:[{$ref:"CollapsibleTrigger",children:[{$ref:"Button",props:{variant:"outline",size:"sm",children:"Toggle"}}]},{$ref:"CollapsibleContent",children:[{$ref:"Card",props:{className:"mt-2 p-2 ring-0 shadow-none bg-muted text-xs"},children:[{$ref:"Text",props:{className:"text-xs",children:"Hidden content revealed."}}]}]}]}),Carousel:()=>({$ref:"Carousel",props:{className:"w-56"},children:[{$ref:"CarouselContent",children:[{$ref:"CarouselItem",children:[{$ref:"Card",props:{className:"p-6 bg-primary text-primary-foreground"},children:[{$ref:"Text",props:{className:"text-sm font-medium",children:"Slide 1"}}]}]}]},{$ref:"CarouselPrevious"},{$ref:"CarouselNext"}]}),ScrollArea:()=>({$ref:"ScrollArea",props:{className:"h-24 w-48 rounded-md border border-border p-2"},children:[{$ref:"Card",props:{className:"flex flex-col gap-1 ring-0 shadow-none bg-transparent p-0"},children:Array.from({length:6},(_,i)=>({$ref:"Text",props:{className:"text-xs",children:`Item ${i+1}`}}))}]}),Layer:()=>({$ref:"Card",props:{className:"relative h-24 w-40 bg-muted rounded-md overflow-hidden"},children:[{$ref:"Layer",props:{top:"1rem",left:"1rem"},children:[{$ref:"Card",props:{className:"px-2 py-0.5 rounded-full bg-primary text-primary-foreground ring-0 shadow-none"},children:[{$ref:"Text",props:{className:"text-xs font-medium",children:"Layer"}}]}]}]}),Gradient:()=>({$ref:"Gradient",props:{preset:"sunset",className:"h-20 w-full rounded-md"}}),Image:()=>({$ref:"Image",props:{src:"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 9'><rect width='16' height='9' fill='%23ddd'/></svg>",aspect:"16/9",fill:!1}}),SVG:()=>({$ref:"SVG",props:{viewBox:"0 0 24 24",content:"<circle cx='12' cy='12' r='8' fill='currentColor' opacity='0.2'/><circle cx='12' cy='12' r='4' fill='currentColor'/>",className:"h-12 w-12 text-primary"}}),Chart:()=>({$ref:"Chart",props:{kind:"line",data:[{x:0,y:4},{x:1,y:6},{x:2,y:5},{x:3,y:9},{x:4,y:7},{x:5,y:12}],className:"h-20 w-full"}})};function buildShowcaseTree(componentRef,propOverrides){let isIconSize=typeof propOverrides?.size==="string"&&propOverrides.size.startsWith("icon"),builder=SHOWCASES[componentRef];if(builder){let tree=builder();if(propOverrides&&Object.keys(propOverrides).length>0)tree.props={...tree.props??{},...propOverrides};if(isIconSize&&componentRef==="Button")delete tree.props?.children,tree.children=[{$ref:"Icon",props:{name:"Plus",size:14}}];return tree}return{$ref:componentRef,props:{children:componentRef,...propOverrides??{}}}}var DARK={bg:"#0D0C0A",fg:"#F7F2E6",muted:"#A59C8D",border:"#26221C",code:"#15130E"},LIGHT={bg:"#F7F2E6",fg:"#141210",muted:"#6F665A",border:"#E2D8C4",code:"#FFFDF7"};function escapeHtml(value){return value.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function renderErrorDocument(page){let c=page.dark?DARK:LIGHT;return`<!doctype html>
|
|
174
|
+
${LOADER_MERGE_BODY}`}function realPath(path){try{return realpathSync4(path)}catch{return path}}class LiveBundler{folderRoot;hostAppsFor;liveExtensionsFor;minify;inline;cached=null;cachedApps=null;buildPromise=null;_version=0;constructor(folderRoot,hostAppsFor,liveExtensionsFor,minify=!1,inline=!1){this.folderRoot=folderRoot;this.hostAppsFor=hostAppsFor;this.liveExtensionsFor=liveExtensionsFor;this.minify=minify;this.inline=inline}get version(){return this._version}hostSourceDirs(){let config=this.hostAppsFor(),dirs=new Set;for(let[app,entries]of partitionByApp(this.liveExtensionsFor())){let hostApp=hostAppForExtension(config,app||void 0);if(hostApp===null)continue;let hostRoot=hostAppRootFrom(this.folderRoot,hostApp),realRoot=realPath(hostRoot),nodeModules=join22(realRoot,"node_modules");for(let entry of entries)try{let file=realPath(resolveImport(entry.importPath,hostRoot,aliasPairs(hostApp)));if(file.startsWith(realRoot)&&!file.startsWith(nodeModules))dirs.add(dirname12(file))}catch{}}return Array.from(dirs)}invalidate(){this.cached=null,this.cachedApps=null,this.buildPromise=null,this._version++}async build(){if(this.cached)return this.cached;if(this.buildPromise)return this.buildPromise;return this.buildPromise=this.doBuild().then((result)=>(this.cached=result,this.buildPromise=null,result)).catch((err)=>{this.buildPromise=null;let result={code:EMPTY_MODULE,errors:[{message:err instanceof Error?err.message:String(err)}]};return this.cached=result,result}),this.buildPromise}async buildApp(app){return await this.build(),this.cachedApps?.get(app)??{code:EMPTY_MODULE,errors:[{message:`no live bundle for app "${app}"`}]}}async doBuild(){let groups=partitionByApp(this.liveExtensionsFor());if(groups.size===0)return this.cachedApps=new Map,{code:EMPTY_MODULE,errors:[]};let config=this.hostAppsFor(),apps=new Map;for(let[app,entries]of groups){let hostApp=hostAppForExtension(config,app||void 0);if(hostApp===null){apps.set(app,{code:EMPTY_MODULE,errors:entries.map((e)=>({importPath:e.importPath,message:`${e.id}: extension names app "${app}" but config.hostApps has no such key.`}))});continue}apps.set(app,await bundleComponents({hostRoot:hostAppRootFrom(this.folderRoot,hostApp),entries,aliases:aliasPairs(hostApp),minify:this.minify,cacheKey:`${this.folderRoot}\x00${app}`}))}this.cachedApps=apps;let errors=[...apps.values()].flatMap((r)=>r.errors);if(apps.size===1)return{code:[...apps.values()][0].code,errors};return{code:this.inline?buildInlineLoaderModule([...apps.values()]):buildLoaderModule([...apps.keys()],this._version),errors}}}var screenNotFound=(screenId)=>({kind:"ScreenNotFound",screenId}),boardNotFound=(boardId)=>({kind:"BoardNotFound",boardId}),frameNotFound=(boardId,frameId)=>({kind:"FrameNotFound",boardId,frameId}),boardGroupNotFound=(groupId)=>({kind:"BoardGroupNotFound",groupId}),unknownComponent2=(ref,suggestions,hint)=>({kind:"UnknownComponent",ref,suggestions,...hint!==void 0?{hint}:{}}),invalidPath=(reason,path,hint)=>({kind:"InvalidPath",reason,...path!==void 0?{path}:{},...hint!==void 0?{hint}:{}}),invalidMove=(reason)=>({kind:"InvalidMove",reason}),lastScreen=(screenId)=>({kind:"LastScreen",screenId}),screenIdConflict=(screenId)=>({kind:"ScreenIdConflict",screenId,hint:`Screen "${screenId}" already exists (a route-scan may have scaffolded it as a placeholder). Build into it instead with compose { screenId: "${screenId}", mode: "replace", jsx: \u2026 }. `+"To replace the screen resource itself, remove_screen then add_screen. To create a separate screen, omit `id` (add_screen auto-suffixes a unique one)."}),screenIdExhausted=(base)=>({kind:"ScreenIdExhausted",base}),boardIdConflict=(boardId)=>({kind:"BoardIdConflict",boardId}),boardIdExhausted=(base)=>({kind:"BoardIdExhausted",base}),frameIdConflict=(boardId,frameId)=>({kind:"FrameIdConflict",boardId,frameId}),badRequest=(message,issues)=>{let hint=scalarChildrenHint(issues);return{kind:"BadRequest",message,...issues!==void 0?{issues}:{},...hint!==void 0?{hint}:{}}};function scalarChildrenHint(issues){return hasScalarChildrenIssue(issues)?'Node-level `children` must be an array of nodes. For scalar text, use `props.children` (e.g. props: { children: "Save" }) \u2014 or wrap it: children: ["Save"].':void 0}function hasScalarChildrenIssue(issues){if(!Array.isArray(issues))return!1;return issues.some((issue)=>{if(typeof issue!=="object"||issue===null)return!1;let i=issue;if(i.code==="invalid_type"&&i.expected==="array"&&Array.isArray(i.path)&&i.path.at(-1)==="children")return!0;return Array.isArray(i.errors)&&i.errors.some((branch)=>hasScalarChildrenIssue(branch))})}var snippetNotFound=(snippetId)=>({kind:"SnippetNotFound",snippetId}),snippetParamMismatch=(snippetId,reason,details)=>({kind:"SnippetParamMismatch",snippetId,reason,...details!==void 0?{details}:{}}),snippetCycle=(snippetId,viaPath)=>({kind:"SnippetCycle",snippetId,viaPath}),snippetInUse=(snippetId,screenIds,snippetIds)=>({kind:"SnippetInUse",snippetId,screenIds,snippetIds}),snippetIdConflict=(snippetId)=>({kind:"SnippetIdConflict",snippetId}),idNotFound=(screenId,id,hint)=>({kind:"IdNotFound",screenId,id,...hint!==void 0?{hint}:{}}),idConflict=(screenId,id,paths)=>({kind:"IdConflict",screenId,id,paths}),annotationConflict=(screenId,locator,existingId)=>({kind:"AnnotationConflict",screenId,locator,existingId}),annotationNotFound=(screenId,annotationId)=>({kind:"AnnotationNotFound",screenId,annotationId}),canvasNoteNotFound=(noteId)=>({kind:"CanvasNoteNotFound",noteId}),extensionIdConflict=(extensionId)=>({kind:"ExtensionIdConflict",message:`Extension "${extensionId}" already exists. Use update_extension to patch it.`,extensionId}),extensionNotFound=(extensionId)=>({kind:"ExtensionNotFound",message:`Extension "${extensionId}" doesn't exist.`,extensionId}),extensionInUse=(extensionId,references)=>({kind:"ExtensionInUse",message:`Extension "${extensionId}" is referenced by ${references.length} node${references.length===1?"":"s"} \u2014 remove the usages first.`,extensionId,references});function levenshtein(a,b){let al=a.length,bl=b.length;if(al===0)return bl;if(bl===0)return al;let dp=Array(bl+1);for(let j=0;j<=bl;j++)dp[j]=j;for(let i=1;i<=al;i++){let prev=i-1;dp[0]=i;for(let j=1;j<=bl;j++){let tmp=dp[j]??0,cost=a[i-1]===b[j-1]?0:1;dp[j]=Math.min((dp[j]??0)+1,(dp[j-1]??0)+1,prev+cost),prev=tmp}}return dp[bl]??0}var normalizeRef=(s)=>s.toLowerCase().replace(/[^a-z0-9]/g,"");function nearestRefs(target,refs,n=3){let t=normalizeRef(target);return refs.map((r)=>({r,d:levenshtein(t,normalizeRef(r))})).sort((a,b)=>a.d-b.d).slice(0,n).map((x)=>x.r)}var SNIPPET_TREE_PREFIX="snippet:";function isSnippetTreeId(screenId){return screenId.startsWith(SNIPPET_TREE_PREFIX)}function snippetIdFromTreeId(screenId){return screenId.slice(SNIPPET_TREE_PREFIX.length)}function getScreen(ctx,screenId){if(isSnippetTreeId(screenId)){let snippetId=snippetIdFromTreeId(screenId),snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return err(snippetNotFound(snippetId));return ok({id:screenId,name:snippet.name,tree:snippet.tree})}let s=ctx.folder.screens.get(screenId);return s?ok(s):err(screenNotFound(screenId))}function getBoard(ctx,boardId){let b=ctx.folder.boards.get(boardId);return b?ok(b):err(boardNotFound(boardId))}function resolve9(root,locator,screenId){let path=resolveLocator(root,locator);if(path!==null)return ok(path);if(isIdLocator(locator)){let id=locator.slice(1);return err(idNotFound(screenId,id,id==="root"?'There is no node with id "root". The root node is the empty path []; tools like screenshot/compare_to_url treat the whole screen as the default when `path` is omitted.':void 0))}return err(invalidPath(`No node at path ${JSON.stringify(locator)}`,locator,'Numeric paths go stale after siblings are added, removed, or moved. Re-locate the node with find_nodes (it returns current paths + ids), or give it a stable @id via set_node_id and address it as "@id".'))}function subtreeHasId(node,id){if(!isComponentNode(node))return!1;if(node.$id===id)return!0;return(node.children??[]).some((c)=>subtreeHasId(c,id))}function findInstanceExposingId(ctx,root,id){let hit=null;function walk(node,path){if(hit)return;if(isSnippetInstance(node)){let def=ctx.folder.snippets.get(node.$snippet);if(def&&subtreeHasId(def.tree,id))hit={instanceLocator:node.$id?`@${node.$id}`:JSON.stringify(path),snippetId:node.$snippet};return}if(!isComponentNode(node))return;let kids=node.children??[];for(let i=0;i<kids.length&&!hit;i++){let child=kids[i];if(child)walk(child,[...path,i])}}return walk(root,[]),hit}function resolveWithSnippetHint(ctx,root,locator,screenId){let r=resolve9(root,locator,screenId);if(r.ok||r.error.kind!=="IdNotFound")return r;let found=findInstanceExposingId(ctx,root,r.error.id);if(!found)return r;let hint=`"${r.error.id}" isn't a top-level node, but a node with that id lives inside snippet instance ${found.instanceLocator} (snippet "${found.snippetId}"), which is opaque to @id addressing. To change its props for just this instance: update_snippet_instance { path: "${found.instanceLocator}", innerPath: "@${r.error.id}", propPatch: {\u2026} }. To change it across all instances, edit the snippet `+"body with update_snippet.";return err(idNotFound(screenId,r.error.id,hint))}function getNode(root,locator,screenId){let r=resolve9(root,locator,screenId);if(!r.ok)return r;let n=pathAt(root,r.value);return n?ok(n):err(invalidPath(`No node at path ${JSON.stringify(r.value)}`,r.value,"Numeric paths go stale after siblings change. Re-locate with find_nodes or address by stable @id."))}function getComponentNode(root,locator,screenId){let r=getNode(root,locator,screenId);if(!r.ok)return r;if(!isComponentNode(r.value)){let r2=resolve9(root,locator,screenId),path=r2.ok?r2.value:[];return err(invalidPath(`Node at ${JSON.stringify(path)} is not a component (got ${describe2(r.value)}). For snippet instances use update_snippet_instance.`,path))}return ok(r.value)}function describe2(node){if(isComponentNode(node))return`$ref=${node.$ref}`;if("$snippet"in node)return`$snippet=${node.$snippet}`;return`$param=${node.$param}`}function getExtensions(ctx){return ctx.folder.config.extensions??{}}function libraryIdForScreen(ctx,screen){let id=screen.library;if(id&&ctx.folder.config.libraries?.[id])return id;return ctx.folder.config.defaultLibrary}function providerForScreen2(ctx,screen){return providerForScreen(screen,ctx.providers,ctx.defaultProvider)}function registryForScreen2(ctx,screen){return registryForScreen(screen,ctx.providers,ctx.defaultProvider,getExtensions(ctx),ctx.folder.config.styling?.framework)}function renderPassForScreen2(ctx,screen,theme,dark=!1){return renderPassForScreen(screen,ctx.providers,ctx.defaultProvider,theme,dark)}function ensureKnownComponent(ctx,ref,screen=null){let provider=screen?providerForScreen2(ctx,screen):ctx.defaultProvider;if(ref in provider.registry)return ok(void 0);let extensions=getExtensions(ctx);if(ref in extensions)return ok(void 0);let snippetMatch=[...ctx.folder.snippets.keys()].find((s)=>normalizeRef(s)===normalizeRef(ref));if(snippetMatch)return err(unknownComponent2(ref,[],`"${ref}" is a snippet, not a library component. In MCP use compose with its PascalCase tag; lower-level callers must use a {"$snippet":"${snippetMatch}"} node. "$ref" is only for library components and registered extensions.`));let known=[...Object.keys(provider.registry),...Object.keys(extensions)];return err(unknownComponent2(ref,nearestRefs(ref,known)))}function getSnippet(ctx,snippetId){let s=ctx.folder.snippets?.get(snippetId);return s?ok(s):err(snippetNotFound(snippetId))}function playwrightMissingMessage(msg){return`screenshot: Playwright is not installed. Run \`${CHROMIUM_INSTALL_CMD}\`, then retry \u2014 no server restart needed. Underlying error: ${msg}`}function captureTimeoutMessage(err,op){if(!isCaptureTimeout(err))return null;let msg=err instanceof Error?err.message:"";if(!/screenshot|setContent/i.test(msg))return null;return`${op} timed out rendering the page (capped at 20s). This is almost always a one-off render `+"stall \u2014 a cold web-font fetch or a first heavy paint \u2014 so retry; it usually clears. If it "+"persists, the page has expensive CSS (a large repeating background, heavy blur/shadow) or is very tall: simplify the heavy styles, or capture a smaller region/viewport."}function browserErrorMessage(err){if(err instanceof Error&&err.name==="BrowserMissingError")return err.message;let msg=err instanceof Error?err.message:String(err);if(/Cannot find package 'playwright'|MODULE_NOT_FOUND|chromium/i.test(msg))return playwrightMissingMessage(msg.split(`
|
|
175
|
+
`)[0]??msg);return null}function defaultViewport(folder){let presets=folder.config.viewportPresets,pick=presets.find((p)=>p.name.toLowerCase().includes("desktop"))??presets[0]??{w:1440,h:900};return{w:pick.w,h:pick.h}}function makeLiveUrl(ctx,bundler){return()=>Object.keys(liveExtensions(ctx.folder.config.extensions)).length>0?`/api/live/bundle.js?v=${bundler.version}`:void 0}async function screenMount(ctx,canvasBundler,screen){if(!providerForScreen2(ctx,screen).canvasBundleSpec)return{kind:"none"};let refs=collectSerializedRefs(serializeTree(screen.tree,{snippets:ctx.folder.snippets}));if(refs.length===0)return{kind:"none"};let libraryId=libraryIdForScreen(ctx,screen),extensionIds=new Set(Object.keys(ctx.folder.config.extensions??{})),extensions=refs.filter((ref)=>extensionIds.has(ref));if(extensions.length>0)return{kind:"server",libraryId,reason:`it uses the extension${extensions.length===1?"":"s"} ${extensions.join(", ")}, which ${extensions.length===1?"has":"have"} no browser-canvas source`};let bundle=await canvasBundler.build(libraryId,refs);if(bundle.usable)return{kind:"mounted",libraryId,refs,bundle};let blocked=bundle.diagnostics.filter((entry)=>entry.status==="unavailable"),reason=blocked.length?`${blocked.map((entry)=>entry.id).join(", ")} ${blocked.length===1?"has":"have"} no source that compiles for the browser`:`the browser bundle failed to build: ${bundle.errors[0]?.message??"no components resolved"}`;return{kind:"server",libraryId,reason,bundle}}function blockingErrors(bundle){return(bundle?.diagnostics??[]).filter((entry)=>entry.status==="unavailable").map((entry)=>{let first=entry.errors?.[0]??entry.note??"no browser source";return`${entry.id}: ${first.length>300?`${first.slice(0,300)}\u2026`:first}`})}async function mountDiagnostics(ctx,canvasBundler,screen){let mount=await screenMount(ctx,canvasBundler,screen).catch(()=>{return});if(mount?.kind!=="server")return[];let errors=blockingErrors(mount.bundle);return[{severity:"warning",code:"render/server-fallback",path:[],message:`This screen renders server-side from Velloo's bundled components, not the app's own, because ${mount.reason}. `+"The browser mount is all-or-nothing, so every component on the screen falls back together \u2014 including ones component_status reports as exact."+(errors.length?` ${errors.join(" | ")}`:""),suggestion:"Fix what blocks the listed component (component_status { screen } has the full errors), or replace it; captures will then show the app's components."}]}function makeCanvasBundle(ctx,canvasBundler){return async(screen,theme,dark)=>{let mount=await screenMount(ctx,canvasBundler,screen);if(mount.kind!=="mounted")return;let provider=providerForScreen2(ctx,screen);return{url:`/api/canvas/bundle.js?v=${canvasBundler.version}&lib=${encodeURIComponent(mount.libraryId)}&refs=${encodeURIComponent(mount.refs.join(","))}`,themeOptions:provider.themeToNative?.(theme,dark)??null}}}async function renderForCapture(ctx,screen,opts){let canvasOpt=await opts.canvasBundle(screen,opts.theme,opts.dark),{html}=await renderScreen(screen,opts.theme,{viewport:opts.viewport,snapshotCss:opts.snapshotCss,registry:registryForScreen2(ctx,screen),renderPass:renderPassForScreen2(ctx,screen,opts.theme,opts.dark),snippets:ctx.folder.snippets,customCss:ctx.folder.customCss,baseHref:opts.assetOrigin,liveBundleUrl:opts.liveUrl(),dark:opts.dark,...canvasOpt?{canvasBundle:canvasOpt}:{}});return html}function regionNode(region,rects,scaleFactor,screen){let area=region.w*region.h,best=rects.map((r)=>({path:r.path,x:r.x*scaleFactor,y:r.y*scaleFactor,w:r.w*scaleFactor,h:r.h*scaleFactor})).filter((r)=>{let ix=Math.max(0,Math.min(r.x+r.w,region.x+region.w)-Math.max(r.x,region.x)),iy=Math.max(0,Math.min(r.y+r.h,region.y+region.h)-Math.max(r.y,region.y));return ix*iy>=area*0.5}).sort((a,b)=>a.w*a.h-b.w*b.h)[0];if(!best)return null;let path=best.path===""?[]:best.path.split(".").map(Number),node=pathAt(screen.tree,path);if(!node)return{path};let id=nodeId(node);return{path,...isComponentNode(node)?{ref:node.$ref}:{},...id!==void 0?{id}:{}}}function contentHeightFromRects(rects){let max=0;for(let r of rects)max=Math.max(max,r.y+r.h);return Math.round(max)}function framesShorterThan(ctx,screenId,contentHeight,viewportW){let out=[];for(let board of ctx.folder.boards.values()){if(isArchived(board))continue;for(let frame of board.frames)if(frame.screen===screenId&&frame.w===viewportW&&frame.h<contentHeight)out.push({board:board.id,frame:frame.id,...frame.label?{label:frame.label}:{},frameHeight:frame.h,overflowBy:contentHeight-frame.h})}return out}function createExportRouter(ctxFor,jit,bundler,canvasBundler){let r=new Hono2,pipeline=(c)=>{let ctx=ctxFor();return{folder:ctx.folder,providers:ctx.providers,defaultProvider:ctx.defaultProvider,snapshotCss:()=>jit.build(),assetOrigin:()=>{let host=c.req.header("host");return host?`http://${host}/`:void 0},liveBundleUrl:()=>Object.keys(liveExtensions(ctx.folder.config.extensions)).length>0?`/api/live/bundle.js?v=${bundler.version}`:void 0,canvasBundleFor:makeCanvasBundle(ctx,canvasBundler)}},parseTarget=(raw)=>{let m=/^(.+)\.(png|pdf|html)$/.exec(raw);return m?.[1]&&m[2]?{id:m[1],ext:m[2]}:null},parseOpts=(c)=>{let mode=c.req.query("mode")??"light";if(!["light","dark","compare"].includes(mode))return null;let scale=Math.min(3,Math.max(0.25,Number(c.req.query("scale"))||1)),theme=c.req.query("theme");return{mode,scale,...theme?{theme}:{}}},send=(c,body,ext,name,warnings=[])=>{let headers={"Content-Type":ext==="png"?"image/png":ext==="pdf"?"application/pdf":"text/html; charset=utf-8","Content-Disposition":`attachment; filename="${exportFilename(name,ext)}"`};if(warnings.length>0)headers["x-velloo-export-warnings"]=encodeURIComponent(JSON.stringify(warnings));return c.body(typeof body==="string"?body:new Uint8Array(body),200,headers)},failed=(c,err)=>{let message=err instanceof Error?err.message:String(err),missing=err instanceof Error&&err.name==="BrowserMissingError";return c.json({error:message},missing?503:500)};return r.get("/frame/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <frameId>.png|pdf|html"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark|compare"},400);let p=pipeline(c),found=findFrame(p.folder,target.id);if(!found)return c.json({error:`frame not found: ${target.id}`},404);if(opts.mode==="compare"&&target.ext!=="png")return c.json({error:'mode "compare" is PNG-only'},400);let screen=p.folder.screens.get(found.frame.screen),name=found.frame.label??screen?.name??target.id;try{if(target.ext==="png")return send(c,await exportFramePng(p,found.board,found.frame,opts),"png",name);if(target.ext==="pdf")return send(c,await exportFramePdf(p,found.board,found.frame,opts),"pdf",name);let out=await exportFrameHtml(p,found.board,found.frame,opts);return send(c,out.html,"html",name,out.warnings)}catch(err){return failed(c,err)}}),r.get("/board/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <boardId>.png|pdf|html"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark|compare"},400);if(opts.mode==="compare")return c.json({error:'mode "compare" is frame-only \u2014 export the board light or dark'},400);let p=pipeline(c),board=p.folder.boards.get(target.id);if(!board)return c.json({error:`board not found: ${target.id}`},404);try{if(target.ext==="png")return send(c,await exportBoardPng(p,board,opts),"png",board.name);if(target.ext==="pdf")return send(c,await exportBoardPdf(p,board,opts),"pdf",board.name);let out=await exportBoardHtml(p,board,opts);return send(c,out.html,"html",board.name,out.warnings)}catch(err){return failed(c,err)}}),r.get("/screen/:file",async(c)=>{let target=parseTarget(c.req.param("file"));if(!target)return c.json({error:"expected <screenId>.html"},400);if(target.ext!=="html")return c.json({error:"screen exports are HTML \u2014 export a frame for PNG/PDF"},400);let opts=parseOpts(c);if(!opts)return c.json({error:"mode must be light|dark"},400);let p=pipeline(c),screen=p.folder.screens.get(target.id);if(!screen)return c.json({error:`screen not found: ${target.id}`},404);let preset=p.folder.config.viewportPresets[0]??{name:"default",w:1440,h:900},viewport={w:Number(c.req.query("w"))||preset.w,h:Number(c.req.query("h"))||preset.h};try{let out=await exportScreenHtml(p,screen,{dark:opts.mode==="dark",...opts.theme?{theme:opts.theme}:{},viewport});return send(c,out.html,"html",screen.name||screen.id,out.warnings)}catch(err){return failed(c,err)}}),r}import{randomUUID}from"crypto";import{rm as rm2}from"fs/promises";import{join as join23}from"path";function validateScreenIds(screenId,screen){let first=findDuplicateIds(screen.tree)[0];if(first)return err(idConflict(screenId,first.id,first.paths));return ok(void 0)}async function persistScreen(folder,screenId,screen,coalesceKey){let validated=ScreenSchema.parse(screen),prev=folder.screens.get(screenId)??null;return folder.history.push({kind:"screen",screenId,screen:prev,coalesceKey}),await writeJsonAtomic(join23(folder.root,"screens",`${screenId}.json`),validated),folder.screens.set(screenId,validated),validated}function commitScreen(folder,screenId,screen,coalesceKey){return DoAsync(async function*(){if(yield*$(validateScreenIds(screenId,screen)),isSnippetTreeId(screenId)){let snippetId=snippetIdFromTreeId(screenId),prev=folder.snippets.get(snippetId);if(!prev)return yield*$({ok:!1,error:snippetNotFound(snippetId)});let updated=await persistSnippet(folder,snippetId,{...prev,tree:screen.tree},coalesceKey);return{id:screenId,name:updated.name,tree:updated.tree}}return await persistScreen(folder,screenId,screen,coalesceKey)})}async function deletePersistedScreen(folder,screenId){let prev=folder.screens.get(screenId)??null;folder.history.push({kind:"screen",screenId,screen:prev}),await rm2(join23(folder.root,"screens",`${screenId}.json`),{force:!0}),await rm2(join23(folder.root,"screens",`${screenId}.annotations.json`),{force:!0}),folder.screens.delete(screenId),folder.annotations.delete(screenId)}async function persistBoard(folder,boardId,board){let validated=BoardSchema.parse(board),prev=folder.boards.get(boardId)??null;return folder.history.push({kind:"board",boardId,board:prev}),await writeJsonAtomic(join23(folder.root,"boards",`${boardId}.json`),validated),folder.boards.set(boardId,validated),validated}async function persistBoards(folder,writes){let validated=writes.map(({boardId,board})=>({boardId,board:BoardSchema.parse(board)}));folder.history.push({kind:"boards",boards:validated.map(({boardId})=>({boardId,board:folder.boards.get(boardId)??null}))});for(let{boardId,board}of validated)await writeJsonAtomic(join23(folder.root,"boards",`${boardId}.json`),board),folder.boards.set(boardId,board);return validated.map((v)=>v.board)}async function deletePersistedBoard(folder,boardId){let prev=folder.boards.get(boardId)??null;folder.history.push({kind:"board",boardId,board:prev}),await rm2(join23(folder.root,"boards",`${boardId}.json`),{force:!0}),await rm2(join23(folder.root,"boards",`${boardId}.notes.json`),{force:!0}),folder.boards.delete(boardId),folder.notes.delete(boardId)}async function persistTheme(folder,theme,coalesceKey){let validated=ThemeSchema.parse(theme);return folder.history.push({kind:"theme",themeName:"default",theme:folder.theme,...coalesceKey?{coalesceKey}:{}}),await writeJsonAtomic(join23(folder.root,"theme","default.json"),validated),folder.theme=validated,folder.themes.set("default",validated),validated}async function persistNamedTheme(folder,name,theme,coalesceKey){if(name==="default")return persistTheme(folder,theme,coalesceKey);let validated=ThemeSchema.parse({...theme,name});return folder.history.push({kind:"theme",themeName:name,theme:folder.themes.get(name)??null,...coalesceKey?{coalesceKey}:{}}),await writeJsonAtomic(join23(folder.root,"theme",`${name}.json`),validated),folder.themes.set(name,validated),validated}async function persistSnippet(folder,snippetId,snippet,coalesceKey){let validated=SnippetSchema.parse(snippet),prev=folder.snippets.get(snippetId)??null;return folder.history.push({kind:"snippet",snippetId,snippet:prev,coalesceKey}),await writeJsonAtomic(join23(folder.root,"snippets",`${snippetId}.json`),validated),folder.snippets.set(snippetId,validated),validated}async function deletePersistedSnippet(folder,snippetId){let prev=folder.snippets.get(snippetId);if(prev)folder.history.push({kind:"snippet",snippetId,snippet:prev});await rm2(join23(folder.root,"snippets",`${snippetId}.json`),{force:!0}),folder.snippets.delete(snippetId)}async function persistAnnotations(folder,screenId,annotations){let validated=annotations.map((a)=>AnnotationSchema.parse(a)),path=join23(folder.root,"screens",`${screenId}.annotations.json`);if(validated.length===0)await rm2(path,{force:!0});else await writeJsonAtomic(path,validated);return folder.annotations.set(screenId,validated),validated}async function persistConfig(folder,config){let validated=ConfigSchema.parse(config);return await writeJsonAtomic(join23(folder.root,".design","config.json"),validated),folder.config=validated,validated}async function persistCanvasNotes(folder,boardId,notes){let validated=notes.map((n)=>CanvasNoteSchema.parse(n)),path=join23(folder.root,"boards",`${boardId}.notes.json`);if(validated.length===0)await rm2(path,{force:!0});else await writeJsonAtomic(path,validated);return folder.notes.set(boardId,validated),validated}function newAnnotationId(){return`ann_${randomUUID().replace(/-/g,"").slice(0,8)}`}function sameTarget(treeRoot,a,b){let pa=resolveLocator(treeRoot,a),pb=resolveLocator(treeRoot,b);if(!pa||!pb)return!1;return JSON.stringify(pa)===JSON.stringify(pb)}async function addAnnotation(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId));yield*$(resolve9(screen.tree,args.target.locator,args.screenId));let existing=ctx.folder.annotations.get(args.screenId)??[];for(let a of existing)if(sameTarget(screen.tree,a.target.locator,args.target.locator))return yield*$(err(annotationConflict(args.screenId,args.target.locator,a.id)));let annotation={id:newAnnotationId(),target:args.target,position:args.position??"auto",body:args.body,...args.collapsed!==void 0?{collapsed:args.collapsed}:{},author:args.author??"user"},next=[...existing,annotation];return await persistAnnotations(ctx.folder,args.screenId,next),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{annotation}})}async function updateAnnotation(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.annotations.get(args.screenId);if(!existing)return yield*$(err(ctx.folder.screens.has(args.screenId)?annotationNotFound(args.screenId,args.annotationId):screenNotFound(args.screenId)));let idx=existing.findIndex((a)=>a.id===args.annotationId);if(idx===-1)return yield*$(err(annotationNotFound(args.screenId,args.annotationId)));let next={...existing[idx],...args.patch.body!==void 0?{body:args.patch.body}:{},...args.patch.position!==void 0?{position:args.patch.position}:{}};if(args.patch.collapsed===null)delete next.collapsed;else if(args.patch.collapsed!==void 0)next.collapsed=args.patch.collapsed;let updated=[...existing];return updated[idx]=next,await persistAnnotations(ctx.folder,args.screenId,updated),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{annotation:next}})}async function removeAnnotation(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.annotations.get(args.screenId);if(!existing)return yield*$(err(ctx.folder.screens.has(args.screenId)?annotationNotFound(args.screenId,args.annotationId):screenNotFound(args.screenId)));if(!existing.some((a)=>a.id===args.annotationId))return yield*$(err(annotationNotFound(args.screenId,args.annotationId)));let next=existing.filter((a)=>a.id!==args.annotationId);return await persistAnnotations(ctx.folder,args.screenId,next),ctx.broadcast({type:"annotations-changed",screenId:args.screenId}),{removedId:args.annotationId}})}import{randomUUID as randomUUID2}from"crypto";function newNoteId(){return`note_${randomUUID2().replace(/-/g,"").slice(0,8)}`}var DEFAULT_NOTE_WIDTH=240;function checkAttachment(ctx,board,attachment){let frame=board.frames.find((f)=>f.id===attachment.frameId);if(!frame)return err(frameNotFound(board.id,attachment.frameId));if(frame.screen!==attachment.screenId)return err(invalidPath(`Frame "${attachment.frameId}" shows screen "${frame.screen}", not "${attachment.screenId}"`));let screen=getScreen(ctx,attachment.screenId);if(!screen.ok)return screen;let resolved=resolve9(screen.value.tree,attachment.locator,attachment.screenId);return resolved.ok?ok(void 0):resolved}async function addNote(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(args.attachment)yield*$(checkAttachment(ctx,board,args.attachment));else if(args.x===void 0||args.y===void 0)return yield*$(err(invalidPath("A note without an attachment needs x and y")));let note={id:newNoteId(),...args.x!==void 0?{x:args.x}:{},...args.y!==void 0?{y:args.y}:{},width:args.width??DEFAULT_NOTE_WIDTH,body:args.body,...args.attachment?{attachment:args.attachment}:{}},existing=ctx.folder.notes.get(args.boardId)??[];return await persistCanvasNotes(ctx.folder,args.boardId,[...existing,note]),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{note}})}async function updateNote(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.notes.get(args.boardId)??[],idx=existing.findIndex((n)=>n.id===args.noteId);if(idx===-1)return yield*$(err(canvasNoteNotFound(args.noteId)));let next={...existing[idx],...args.patch.x!==void 0?{x:args.patch.x}:{},...args.patch.y!==void 0?{y:args.patch.y}:{},...args.patch.width!==void 0?{width:args.patch.width}:{},...args.patch.body!==void 0?{body:args.patch.body}:{}},updated=[...existing];return updated[idx]=next,await persistCanvasNotes(ctx.folder,args.boardId,updated),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{note:next}})}async function removeNote(ctx,args){return DoAsync(async function*(){let existing=ctx.folder.notes.get(args.boardId)??[];if(!existing.some((n)=>n.id===args.noteId))return yield*$(err(canvasNoteNotFound(args.noteId)));let next=existing.filter((n)=>n.id!==args.noteId);return await persistCanvasNotes(ctx.folder,args.boardId,next),ctx.broadcast({type:"notes-changed",boardId:args.boardId}),{removedId:args.noteId}})}function createLockMap(){let chains=new Map;return{run(key,fn){let next=(chains.get(key)??Promise.resolve()).then(fn,fn),tail=next.then(()=>{return},()=>{return});return chains.set(key,tail),tail.then(()=>{if(chains.get(key)===tail)chains.delete(key)}),next},get size(){return chains.size}}}function broadcastTreeChange(ctx,screenId){if(isSnippetTreeId(screenId))ctx.broadcast({type:"snippet-changed",snippetId:snippetIdFromTreeId(screenId)});else ctx.broadcast({type:"screen-changed",screenId})}var locks=createLockMap(),lockKey=(folder,kind,id)=>`${folder.root}\x00${kind}\x00${id}`;function withScreenLock(folder,screenId,fn){if(isSnippetTreeId(screenId))return locks.run(lockKey(folder,"snippet",snippetIdFromTreeId(screenId)),fn);return locks.run(lockKey(folder,"screen",screenId),fn)}function withSnippetLock(folder,snippetId,fn){return locks.run(lockKey(folder,"snippet",snippetId),fn)}function withConfigLock(folder,fn){return locks.run(lockKey(folder,"config","config"),fn)}function withBoardLock(folder,boardId,fn){return locks.run(lockKey(folder,"board",boardId),fn)}function withBoardLocks(folder,boardIds,fn){let[head,...rest]=[...new Set(boardIds)].sort();if(head===void 0)return fn();return withBoardLock(folder,head,()=>withBoardLocks(folder,rest,fn))}function addAnnotation2(ctx,args){return tracked(ctx,"add_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>addAnnotation(ctx,args)))}function updateAnnotation2(ctx,args){return tracked(ctx,"update_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateAnnotation(ctx,args)))}function removeAnnotation2(ctx,args){return tracked(ctx,"remove_annotation",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeAnnotation(ctx,args)))}function addNote2(ctx,args){return tracked(ctx,"add_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>addNote(ctx,args)))}function updateNote2(ctx,args){return tracked(ctx,"update_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateNote(ctx,args)))}function removeNote2(ctx,args){return tracked(ctx,"remove_note",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeNote(ctx,args)))}function slugify(s,fallback){return s.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,48)||fallback}var MAX_GROUP_NAME_LENGTH=40,GROUP_COLORS=["#8b5cf6","#0ea5e9","#f59e0b","#f43f5e","#10b981","#6366f1","#ec4899","#84cc16"];function nextGroupColor(existing){let used=new Set(existing.map((g)=>g.color));return GROUP_COLORS.find((c)=>!used.has(c))??GROUP_COLORS[existing.length%8]}function boardGroups(config){return config.boardGroups??[]}function nameTooLong(name){if(name.length<=MAX_GROUP_NAME_LENGTH)return null;return badRequest(`group name too long (${name.length} chars) \u2014 max ${MAX_GROUP_NAME_LENGTH} characters`)}function uniqueGroupId(groups,base){let id=base,attempt=2;while(groups.some((g)=>g.id===id))id=`${base}-${attempt++}`;return id}function resolveGroup(config,ref){let trimmed=ref.trim();if(trimmed.length===0)return err(badRequest("group name is empty"));let tooLong=nameTooLong(trimmed);if(tooLong)return err(tooLong);let groups=boardGroups(config),match=groups.find((g)=>g.id===trimmed)??groups.find((g)=>g.name.toLowerCase()===trimmed.toLowerCase());if(match)return ok({config,groupId:match.id});let group={id:uniqueGroupId(groups,slugify(trimmed,"group")),name:trimmed,color:nextGroupColor(groups)};return ok({config:{...config,boardGroups:[...groups,group]},groupId:group.id})}async function addBoardGroup(ctx,args){return DoAsync(async function*(){let name=args.name.trim();if(name.length===0)return yield*$(err(badRequest("group name is empty")));let tooLong=nameTooLong(name);if(tooLong)return yield*$(err(tooLong));let groups=boardGroups(ctx.folder.config),group={id:uniqueGroupId(groups,slugify(name,"group")),name,color:args.color??nextGroupColor(groups)};return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:[...groups,group]}),ctx.broadcast({type:"config-changed"}),{group}})}async function updateBoardGroup(ctx,args){return DoAsync(async function*(){let groups=boardGroups(ctx.folder.config),current=groups.find((g)=>g.id===args.groupId);if(!current)return yield*$(err(boardGroupNotFound(args.groupId)));let next={...current};if(args.patch.name!==void 0){let name=args.patch.name.trim();if(name.length===0)return yield*$(err(badRequest("group name is empty")));let tooLong=nameTooLong(name);if(tooLong)return yield*$(err(tooLong));next.name=name}if(args.patch.color!==void 0)if(args.patch.color===null)delete next.color;else next.color=args.patch.color;return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:groups.map((g)=>g.id===args.groupId?next:g)}),ctx.broadcast({type:"config-changed"}),{group:next}})}async function removeBoardGroup(ctx,args){return DoAsync(async function*(){let groups=boardGroups(ctx.folder.config);if(!groups.some((g)=>g.id===args.groupId))return yield*$(err(boardGroupNotFound(args.groupId)));let ungroupedBoardIds=[];for(let[boardId,board]of ctx.folder.boards){if(board.group!==args.groupId)continue;let{group:_group,...rest}=board;await persistBoard(ctx.folder,boardId,rest),ungroupedBoardIds.push(boardId)}let remaining=groups.filter((g)=>g.id!==args.groupId);await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:remaining.length>0?remaining:void 0});for(let boardId of ungroupedBoardIds)ctx.broadcast({type:"board-changed",boardId});return ctx.broadcast({type:"config-changed"}),{removedGroupId:args.groupId,ungroupedBoardIds}})}async function reorderBoardGroups(ctx,args){let groups=boardGroups(ctx.folder.config),seen=new Set,next=[];for(let id of args.order){let group=groups.find((g)=>g.id===id);if(group&&!seen.has(id))next.push(group),seen.add(id)}for(let group of groups)if(!seen.has(group.id))next.push(group);return await persistConfig(ctx.folder,{...ctx.folder.config,boardGroups:next}),ctx.broadcast({type:"config-changed"}),ok({order:next.map((g)=>g.id)})}function addBoardGroup2(ctx,args){return tracked(ctx,"add_board_group",{},()=>addBoardGroup(ctx,args))}function updateBoardGroup2(ctx,args){return tracked(ctx,"update_board_group",{},()=>updateBoardGroup(ctx,args))}function removeBoardGroup2(ctx,args){return tracked(ctx,"remove_board_group",{},()=>removeBoardGroup(ctx,args))}function reorderBoardGroups2(ctx,args){return tracked(ctx,"reorder_board_groups",{},()=>reorderBoardGroups(ctx,args))}function screensPlacedOnlyOn(folder,boardId){let board=folder.boards.get(boardId);if(!board)return[];let elsewhere=new Set;for(let[otherId,other]of folder.boards){if(otherId===boardId)continue;for(let frame of other.frames)elsewhere.add(frame.screen)}let only=new Set;for(let frame of board.frames){if(elsewhere.has(frame.screen))continue;if(!folder.screens.has(frame.screen))continue;only.add(frame.screen)}return[...only]}function boardNameTooLong(name){if(name.length<=MAX_BOARD_NAME_LENGTH)return null;return badRequest(`board name too long (${name.length} chars) \u2014 max ${MAX_BOARD_NAME_LENGTH} characters`)}async function addBoard(ctx,args){return DoAsync(async function*(){let tooLong=boardNameTooLong(args.name);if(tooLong)return yield*$(err(tooLong));let baseId=args.id??slugify(args.name,"board");if(args.id!==void 0&&ctx.folder.boards.has(args.id))return yield*$(err(boardIdConflict(args.id)));let boardId=baseId,attempt=2;while(ctx.folder.boards.has(boardId))if(boardId=`${baseId}-${attempt++}`,attempt>100)return yield*$(err(boardIdExhausted(baseId)));let groupId;if(args.group!==void 0){let resolved=yield*$(resolveGroup(ctx.folder.config,args.group));if(groupId=resolved.groupId,resolved.config!==ctx.folder.config)await persistConfig(ctx.folder,resolved.config),ctx.broadcast({type:"config-changed"})}let board={id:boardId,name:args.name,...groupId?{group:groupId}:{},frames:[],groups:[]};return await persistBoard(ctx.folder,boardId,board),ctx.broadcast({type:"board-changed",boardId}),{boardId,board}})}async function updateBoard(ctx,args){return DoAsync(async function*(){if(args.patch.name!==void 0){let tooLong=boardNameTooLong(args.patch.name);if(tooLong)return yield*$(err(tooLong))}let next={...yield*$(getBoard(ctx,args.boardId))};if(args.patch.name!==void 0)next.name=args.patch.name;if(args.patch.theme!==void 0)if(args.patch.theme===null)delete next.theme;else next.theme=args.patch.theme;if(args.patch.group!==void 0)if(args.patch.group===null)delete next.group;else{let resolved=yield*$(resolveGroup(ctx.folder.config,args.patch.group));if(next.group=resolved.groupId,resolved.config!==ctx.folder.config)await persistConfig(ctx.folder,resolved.config),ctx.broadcast({type:"config-changed"})}if(args.patch.archived!==void 0)if(args.patch.archived)next.archivedAt=new Date().toISOString();else delete next.archivedAt;return await persistBoard(ctx.folder,args.boardId,next),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{board:next}})}async function reorderBoards(ctx,args){let seen=new Set,moved=[];for(let id of args.order)if(ctx.folder.boards.has(id)&&!seen.has(id))moved.push(id),seen.add(id);let current=orderedBoards(ctx.folder).map(([id])=>id),next=0,order=current.map((id)=>seen.has(id)?moved[next++]:id);for(let id of moved.slice(next))order.push(id);let nextConfig={...ctx.folder.config,boardOrder:order};return await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),ok({order})}async function removeBoard(ctx,args){return DoAsync(async function*(){yield*$(getBoard(ctx,args.boardId));let orphaned=screensPlacedOnlyOn(ctx.folder,args.boardId),strandedSnippets=snippetsStrandedByRemoving(ctx.folder,orphaned);await deletePersistedBoard(ctx.folder,args.boardId);for(let screenId of orphaned)await withScreenLock(ctx.folder,screenId,()=>deletePersistedScreen(ctx.folder,screenId)),ctx.broadcast({type:"screen-changed",screenId});for(let snippetId of strandedSnippets)await deletePersistedSnippet(ctx.folder,snippetId),ctx.broadcast({type:"snippet-changed",snippetId});let order=ctx.folder.config.boardOrder;if(order?.includes(args.boardId)){let pruned=order.filter((id)=>id!==args.boardId),nextConfig={...ctx.folder.config,boardOrder:pruned.length>0?pruned:void 0};await persistConfig(ctx.folder,nextConfig)}return ctx.broadcast({type:"board-changed",boardId:args.boardId}),{removedBoardId:args.boardId,removedScreenIds:orphaned,removedSnippetIds:strandedSnippets}})}function snippetsStrandedByRemoving(folder,screenIds){if(screenIds.length===0)return[];let going=new Set(screenIds),before=reachableSnippetIds(folder,folder.screens.values()),after=reachableSnippetIds(folder,[...folder.screens].filter(([id])=>!going.has(id)).map(([,screen])=>screen));return[...before].filter((id)=>!after.has(id)&&folder.snippets.has(id)).sort()}function addBoard2(ctx,args){return tracked(ctx,"add_board",(v)=>({boardId:v.boardId}),()=>addBoard(ctx,args))}function updateBoard2(ctx,args){return tracked(ctx,"update_board",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateBoard(ctx,args)))}function removeBoard2(ctx,args){return tracked(ctx,"remove_board",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeBoard(ctx,args)))}function reorderBoards2(ctx,args){return tracked(ctx,"reorder_boards",{},()=>reorderBoards(ctx,args))}async function commit(ctx,next){let saved=await persistConfig(ctx.folder,next);return ctx.broadcast({type:"config-changed"}),saved}async function updateViewportPresets(ctx,args){let parsed=ViewportPresetSchema.array().safeParse(args.presets);if(!parsed.success)return err(badRequest("Each viewport preset needs a name and positive w/h.",parsed.error.issues));let presets=parsed.data.map((p)=>({...p,name:p.name.trim()}));if(presets.length===0)return err(badRequest("A folder needs at least one viewport preset."));if(presets.some((p)=>p.name===""))return err(badRequest("A viewport preset needs a name."));let seen=new Set;for(let p of presets){let key=p.name.toLowerCase();if(seen.has(key))return err(badRequest(`Two viewport presets are both named "${p.name}".`));seen.add(key)}return await commit(ctx,{...ctx.folder.config,viewportPresets:presets}),ok({presets})}async function updateDefaults(ctx,args){let next={...ctx.folder.config};if(args.defaultBoard!==void 0)if(args.defaultBoard===null)delete next.defaultBoard;else{if(!ctx.folder.boards.has(args.defaultBoard))return err(boardNotFound(args.defaultBoard));next.defaultBoard=args.defaultBoard}if(args.defaultScreen!==void 0)if(args.defaultScreen===null)delete next.defaultScreen;else{if(!ctx.folder.screens.has(args.defaultScreen))return err(screenNotFound(args.defaultScreen));next.defaultScreen=args.defaultScreen}let saved=await commit(ctx,next);return ok({defaultBoard:saved.defaultBoard??null,defaultScreen:saved.defaultScreen??null})}async function updateDesignName(ctx,args){let name=args.name.trim(),previous=ctx.folder.config.name;if(name===previous)return ok({name});let conflict=await designRenameConflict(ctx.folder.root,name);if(conflict)return err(badRequest(conflict));let saved=await commit(ctx,{...ctx.folder.config,name});return await followDesignRename(ctx.folder.root,previous,name),ok({name:saved.name})}async function updateCodegen(ctx,args){let next={...ctx.folder.config};if(args.componentsAlias!==void 0){let alias=args.componentsAlias?.trim()??null;if(alias==="")return err(badRequest("The components alias can't be empty \u2014 clear it to use the default."));let codegen={...next.codegen,...alias===null?{}:{componentsAlias:alias}};if(alias===null)delete codegen.componentsAlias;if(Object.keys(codegen).length===0)delete next.codegen;else next.codegen=codegen}let saved=await commit(ctx,next);return ok({componentsAlias:saved.codegen?.componentsAlias??null})}async function updateFeedback(ctx,args){let current=ctx.folder.config.feedback,contactOk=args.contactOk??await readFeedbackContactOk(),feedback={enabled:args.enabled??current?.enabled??!1,contactOk};if(args.contactOk!==void 0)await writeFeedbackContactOk(args.contactOk);if(await writeRepoFeedback(ctx.folder.root,feedback))return ctx.folder.config={...ctx.folder.config,feedback},ctx.broadcast({type:"config-changed"}),ok({enabled:feedback.enabled,contactOk:feedback.contactOk});let saved=await commit(ctx,{...ctx.folder.config,feedback:{enabled:feedback.enabled}});return ok({enabled:saved.feedback?.enabled??!1,contactOk:feedback.contactOk})}function updateViewportPresets2(ctx,args){return tracked(ctx,"update_viewport_presets",{},()=>withConfigLock(ctx.folder,()=>updateViewportPresets(ctx,args)))}function updateDefaults2(ctx,args){return tracked(ctx,"update_defaults",{},()=>withConfigLock(ctx.folder,()=>updateDefaults(ctx,args)))}function updateDesignName2(ctx,args){return tracked(ctx,"update_design_name",{},()=>withConfigLock(ctx.folder,()=>updateDesignName(ctx,args)))}function updateCodegen2(ctx,args){return tracked(ctx,"update_codegen",{},()=>withConfigLock(ctx.folder,()=>updateCodegen(ctx,args)))}function updateFeedback2(ctx,args){return tracked(ctx,"update_feedback",{},()=>withConfigLock(ctx.folder,()=>updateFeedback(ctx,args)))}function autoPosition(frames){if(frames.length===0)return{x:0,y:0};let maxRight=0;for(let f of frames)if(f.x+f.w>maxRight)maxRight=f.x+f.w;return{x:maxRight+80,y:0}}function genFrameId(board,screenId){let existing=new Set(board.frames.map((f)=>f.id)),i=1;while(existing.has(`${screenId}-${i}`))i++;return`${screenId}-${i}`}async function addFrame(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(yield*$(getScreen(ctx,args.screenId)),args.id!==void 0&&board.frames.some((f)=>f.id===args.id))return yield*$(err(frameIdConflict(args.boardId,args.id)));let id=args.id??genFrameId(board,args.screenId),pos=args.x!==void 0&&args.y!==void 0?{x:args.x,y:args.y}:autoPosition(board.frames),frame={id,screen:args.screenId,x:pos.x,y:pos.y,w:args.w,h:args.h,...args.label!==void 0?{label:args.label}:{},...args.group!==void 0?{group:args.group}:{}},nextBoard={...board,frames:[...board.frames,frame]};return await persistBoard(ctx.folder,args.boardId,nextBoard),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frame}})}async function moveFrame(ctx,args){return DoAsync(async function*(){let from=yield*$(getBoard(ctx,args.boardId)),to=yield*$(getBoard(ctx,args.toBoardId));if(args.boardId===args.toBoardId)return yield*$(err(invalidMove(`Frame "${args.frameId}" is already on board "${args.boardId}" \u2014 use update_frame to reposition it.`)));let frame=from.frames.find((f)=>f.id===args.frameId);if(!frame)return yield*$(err(frameNotFound(args.boardId,args.frameId)));let pos=args.x!==void 0&&args.y!==void 0?{x:args.x,y:args.y}:autoPosition(to.frames),taken=to.frames.some((f)=>f.id===frame.id),{group:_dropped,...carried}=frame,moved={...carried,id:taken?genFrameId(to,frame.screen):frame.id,x:pos.x,y:pos.y};return await persistBoards(ctx.folder,[{boardId:args.toBoardId,board:{...to,frames:[...to.frames,moved]}},{boardId:args.boardId,board:{...from,frames:from.frames.filter((f)=>f.id!==args.frameId)}}]),ctx.broadcast({type:"board-changed",boardId:args.toBoardId}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frame:moved,fromBoardId:args.boardId,toBoardId:args.toBoardId}})}async function removeFrame(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId));if(board.frames.findIndex((f)=>f.id===args.frameId)===-1)return yield*$(err(frameNotFound(args.boardId,args.frameId)));let nextFrames=board.frames.filter((f)=>f.id!==args.frameId);return await persistBoard(ctx.folder,args.boardId,{...board,frames:nextFrames}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{removedFrameId:args.frameId}})}function applyPatch2(frame,patch){let next={...frame};if(patch.x!==void 0)next.x=patch.x;if(patch.y!==void 0)next.y=patch.y;if(patch.w!==void 0)next.w=patch.w;if(patch.h!==void 0)next.h=patch.h;if(patch.label!==void 0)if(patch.label===null)delete next.label;else next.label=patch.label;if(patch.group!==void 0)if(patch.group===null)delete next.group;else next.group=patch.group;if(patch.scheme!==void 0)if(patch.scheme===null)delete next.scheme;else next.scheme=patch.scheme;return next}async function updateFrames(ctx,args){return DoAsync(async function*(){let board=yield*$(getBoard(ctx,args.boardId)),nextFrames=[...board.frames];for(let{frameId,patch}of args.patches){let idx=nextFrames.findIndex((f)=>f.id===frameId);if(idx===-1)return yield*$(err(frameNotFound(args.boardId,frameId)));nextFrames[idx]=applyPatch2(nextFrames[idx],patch)}return await persistBoard(ctx.folder,args.boardId,{...board,frames:nextFrames}),ctx.broadcast({type:"board-changed",boardId:args.boardId}),{frames:nextFrames}})}function addFrame2(ctx,args){return tracked(ctx,"add_frame",(v)=>({boardId:args.boardId,frameId:v.frame.id,screenId:v.frame.screen}),()=>withBoardLock(ctx.folder,args.boardId,()=>addFrame(ctx,args)))}function updateFrames2(ctx,args){return tracked(ctx,"update_frame",{boardId:args.boardId},()=>withBoardLock(ctx.folder,args.boardId,()=>updateFrames(ctx,args)))}function removeFrame2(ctx,args){return tracked(ctx,"remove_frame",{boardId:args.boardId,frameId:args.frameId},()=>withBoardLock(ctx.folder,args.boardId,()=>removeFrame(ctx,args)))}function moveFrame2(ctx,args){return tracked(ctx,"move_frame",(v)=>({boardId:v.toBoardId,frameId:v.frame.id,screenId:v.frame.screen}),()=>withBoardLocks(ctx.folder,[args.boardId,args.toBoardId],()=>moveFrame(ctx,args)))}var import_react4=__toESM(require_react_development(),1);function findNodeById(root,id){if(!isComponentNode(root))return;if(root.$id===id)return root;for(let child of root.children??[]){let hit=findNodeById(child,id);if(hit)return hit}return}function innerPathResolves(body,innerPath){if(innerPath.startsWith("@"))return findNodeById(body,innerPath.slice(1))!==void 0;let segments=innerPath===""?[]:innerPath.split(".").map(Number),cursor=body;for(let i of segments){if(!cursor||!isComponentNode(cursor)||!cursor.children)return!1;cursor=cursor.children[i]}return cursor!==void 0&&isComponentNode(cursor)}function resolveInnerNode(body,innerPath,snippetId){let target;if(innerPath.startsWith("@"))target=findNodeById(body,innerPath.slice(1));else{let segments=innerPath===""?[]:innerPath.split(".").map(Number);target=body;for(let i of segments){if(!target||!isComponentNode(target)||!target.children){target=void 0;break}target=target.children[i]}}if(!target||!isComponentNode(target))return err(invalidPath(`innerPath "${innerPath}" doesn't resolve to a component inside snippet "${snippetId}"`));return ok(target)}var SUBJECT_REF="__vellooInspectSubject",SUBJECT_TAG="velloo-inspect-subject";function Subject({children}){return import_react4.createElement(SUBJECT_TAG,null,children)}function virtualScreen(screen,tree){return{id:`${screen.id}__inspect`,name:`${screen.name} inspect`,library:screen.library,tree}}function renderIsolated(ctx,screen,node){let subScreen=virtualScreen(screen,node);return renderBodyGuarded(subScreen,registryForScreen2(ctx,subScreen),ctx.folder.snippets)}function markSubject(root,path){if(path.length===0)return{$ref:SUBJECT_REF,children:[root]};if(!isComponentNode(root))return null;let[index,...rest]=path,children=root.children;if(index===void 0||!children)return null;let child=children[index];if(!child)return null;let marked=markSubject(child,rest);if(!marked)return null;let next=[...children];return next[index]=marked,{...root,children:next}}function subjectMarkup(html){let open=`<${SUBJECT_TAG}>`,start=html.indexOf(open);if(start===-1)return null;let end=html.indexOf(`</${SUBJECT_TAG}>`,start);return end===-1?null:html.slice(start+open.length,end)}function renderInPlace(ctx,screen,root,path){let marked=markSubject(root,path);if(!marked)return null;let subScreen=virtualScreen(screen,marked),registry={...registryForScreen2(ctx,subScreen),[SUBJECT_REF]:Subject},{html}=renderBodyGuarded(subScreen,registry,ctx.folder.snippets);return subjectMarkup(html)}function inspectComponent(ctx,screen,node,site,note){let isolated=renderIsolated(ctx,screen,node),bodyHtml=isolated.failures.length>0?renderInPlace(ctx,screen,site.root,site.path)??isolated.html:isolated.html,className=node.props?.className??"",classes=typeof className==="string"?className.trim().split(/\s+/).filter(Boolean):[];return{ref:node.$ref,resolvedProps:{...node.props??{}},classes,bodyHtml,...note?{note}:{}}}async function inspect(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),node=yield*$(getNode(screen.tree,args.path,args.screenId));if(isSnippetInstance(node)){let snippet=ctx.folder.snippets.get(node.$snippet);if(!snippet)return yield*$(err(snippetNotFound(node.$snippet)));let innerPath=args.innerPath??"",body=resolveSnippetBody(node,snippet),inner=yield*$(resolveInnerNode(body,innerPath,node.$snippet)),note=innerPath===""?`Node is the body root of snippet instance "${node.$snippet}". Pass innerPath ("@id" or a dotted index like "0.2") to inspect a deeper node inside the body.`:`Node is inside snippet instance "${node.$snippet}" at innerPath "${innerPath}".`,innerSite=resolveLocator(body,innerPath===""?[]:innerPath)??[];return inspectComponent(ctx,screen,inner,{root:body,path:innerSite},note)}if(args.innerPath!==void 0&&args.innerPath!=="")return yield*$(err(invalidPath(`innerPath "${args.innerPath}" was given but the node at ${JSON.stringify(args.path)} is not a snippet instance`)));if(!isComponentNode(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(args.path)} is not inspectable (expected a component or snippet instance)`)));let site=yield*$(resolve9(screen.tree,args.path,args.screenId));return inspectComponent(ctx,screen,node,{root:screen.tree,path:site},void 0)})}function inspect2(ctx,args){return inspect(ctx,args)}function cloneNode(n){return JSON.parse(JSON.stringify(n))}function cloneScreen(s){return JSON.parse(JSON.stringify(s))}async function addScreen(ctx,args){return DoAsync(async function*(){let baseId=args.id??slugify(args.name,"screen");if(args.id!==void 0&&ctx.folder.screens.has(args.id))return yield*$(err(screenIdConflict(args.id)));let screenId=baseId,attempt=2;while(ctx.folder.screens.has(screenId))if(screenId=`${baseId}-${attempt++}`,attempt>100)return yield*$(err(screenIdExhausted(baseId)));let tree;if(args.fromScreenId!==void 0){let src=yield*$(getScreen(ctx,args.fromScreenId));tree=cloneScreen(src).tree}else if(args.tree!==void 0)tree=cloneNode(args.tree);else tree={$ref:"Card",props:{className:"p-6"}};let screen={id:screenId,name:args.name,tree};return await persistScreen(ctx.folder,screenId,screen),ctx.broadcast({type:"screen-changed",screenId}),{screenId,screen}})}async function removeScreen(ctx,args){return DoAsync(async function*(){if(yield*$(getScreen(ctx,args.screenId)),ctx.folder.screens.size<=1)return yield*$(err(lastScreen(args.screenId)));let removedFrames=[];for(let[boardId,board]of ctx.folder.boards){let referencing=board.frames.filter((f)=>f.screen===args.screenId);if(referencing.length===0)continue;let nextBoard={...board,frames:board.frames.filter((f)=>f.screen!==args.screenId)};await persistBoard(ctx.folder,boardId,nextBoard),ctx.broadcast({type:"board-changed",boardId}),removedFrames.push({boardId,frameIds:referencing.map((f)=>f.id)})}return await deletePersistedScreen(ctx.folder,args.screenId),ctx.broadcast({type:"screen-changed",screenId:args.screenId}),{removedScreenId:args.screenId,removedFrames}})}async function setScreenTree(ctx,args){let{screenId}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),prevRef="$ref"in screen.tree?screen.tree.$ref:"(root)",next=cloneScreen(screen);return next.tree=cloneNode(args.tree),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{screenId,replacedRootRef:prevRef}})}async function updateScreen(ctx,args){return DoAsync(async function*(){if(isSnippetTreeId(args.screenId))return yield*$(err(badRequest(`${args.screenId} is a snippet body, not a screen \u2014 rename it with update_snippet.`)));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen);if(args.patch.name!==void 0)next.name=args.patch.name;return await persistScreen(ctx.folder,args.screenId,next),ctx.broadcast({type:"screen-changed",screenId:args.screenId}),{screen:next}})}function addScreen2(ctx,args){return tracked(ctx,"add_screen",(v)=>({screenId:v.screenId}),()=>addScreen(ctx,args))}function removeScreen2(ctx,args){return tracked(ctx,"remove_screen",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeScreen(ctx,args)))}function updateScreen2(ctx,args){return tracked(ctx,"update_screen",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateScreen(ctx,args)))}function setScreenTree2(ctx,args){return tracked(ctx,"set_screen_tree",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>setScreenTree(ctx,args)))}function detectSnippetCycle(root,rootId,registry){let stack=[rootId],visited=new Set([rootId]);function walk(node){if(isSnippetInstance(node)){let id=node.$snippet;if(visited.has(id))return[...stack,id];let inner=registry.get(id);if(!inner)return null;visited.add(id),stack.push(id);let found=walk(inner.tree);if(stack.pop(),visited.delete(id),found)return found;return null}if(node.children)for(let child of node.children){let found=walk(child);if(found)return found}return null}return walk(root)}async function addSnippet(ctx,args){let id=args.id??slugify(args.name,"snippet");if(ctx.folder.snippets.has(id))return err(snippetIdConflict(id));let snippet={id,name:args.name,params:args.params??[],tree:args.tree},registry=new Map(ctx.folder.snippets);registry.set(id,snippet);let cycle=detectSnippetCycle(args.tree,id,registry);if(cycle)return err(snippetCycle(id,cycle));let persisted=await persistSnippet(ctx.folder,id,snippet);return ctx.broadcast({type:"snippet-changed",snippetId:id}),ok({snippetId:id,snippet:persisted})}async function instantiateSnippet(ctx,args){return DoAsync(async function*(){let snippet=yield*$(getSnippet(ctx,args.snippetId));yield*$(validateArgs(snippet,args.args??{})),yield*$(validateOverrides(snippet,args.overrides));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolvedParent=yield*$(resolve9(next.tree,args.parentPath,args.screenId)),parent=yield*$(getComponentNode(next.tree,resolvedParent,args.screenId));if(!parent.children)parent.children=[];let idx=args.index??parent.children.length;if(idx<0||idx>parent.children.length)return yield*$(err(invalidPath(`index ${idx} out of range for parent with ${parent.children.length} children`,resolvedParent)));let node={$snippet:snippet.id,...args.id!==void 0?{$id:args.id}:{},...args.extraClassName&&args.extraClassName.trim()!==""?{$extraClassName:args.extraClassName.trim()}:{},...args.args&&Object.keys(args.args).length>0?{args:args.args}:{},...args.overrides&&Object.keys(args.overrides).length>0?{$overrides:args.overrides}:{}};return parent.children.splice(idx,0,node),yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:[...resolvedParent,idx]}})}function validateArgs(snippet,passed){let declared=new Set(snippet.params.map((p)=>p.name)),{missing}=resolveSnippetArgs(snippet,passed),extras=Object.keys(passed).filter((k)=>!declared.has(k));if(missing.length===0&&extras.length===0)return{ok:!0,value:void 0};let parts=[];if(missing.length>0)parts.push(`missing required: ${missing.join(", ")}`);if(extras.length>0)parts.push(`unknown: ${extras.join(", ")}`);return{ok:!1,error:snippetParamMismatch(snippet.id,parts.join("; "),{missing,extras})}}function validateOverrides(snippet,overrides){if(!overrides)return{ok:!0,value:void 0};let bad=Object.keys(overrides).filter((key)=>!innerPathResolves(snippet.tree,key));if(bad.length===0)return{ok:!0,value:void 0};return{ok:!1,error:invalidPath(`overrides target nodes not found in snippet "${snippet.id}": ${bad.join(", ")} \u2014 `+'each key must be the "@id" of a body node, a dotted index path like "0.2", or "" for the root')}}async function removeSnippet(ctx,args){return DoAsync(async function*(){yield*$(getSnippet(ctx,args.snippetId));let{screenIds,snippetIds}=snippetReferencers(ctx.folder,args.snippetId);if(screenIds.length>0||snippetIds.length>0)return yield*$(err(snippetInUse(args.snippetId,screenIds,snippetIds)));return await deletePersistedSnippet(ctx.folder,args.snippetId),ctx.broadcast({type:"snippet-changed",snippetId:args.snippetId}),{removedId:args.snippetId}})}async function updateSnippet(ctx,args){return DoAsync(async function*(){let prev=yield*$(getSnippet(ctx,args.snippetId)),{innerPatch}=args.patch,tree=args.patch.tree??prev.tree;if(innerPatch!==void 0){if(!/^$|^\d+(\.\d+)*$|^@[a-zA-Z][a-zA-Z0-9_-]*$/.test(innerPatch.innerPath))return yield*$(err(invalidPath('innerPath must be a dotted index path like "0.2", an "@id" of a body node, or "" for the root')));if(!innerPathResolves(tree,innerPatch.innerPath))return yield*$(err(invalidPath(`innerPath "${innerPatch.innerPath}" doesn't resolve to a component inside snippet "${args.snippetId}"`)));tree=applySnippetOverrides(tree,{[innerPatch.innerPath]:{props:innerPatch.propPatch}})}let treeChanged=args.patch.tree!==void 0||innerPatch!==void 0,next={...prev,...args.patch.name!==void 0?{name:args.patch.name}:{},...args.patch.params!==void 0?{params:args.patch.params}:{},...treeChanged?{tree}:{}};if(args.patch.tree!==void 0){let registry=new Map(ctx.folder.snippets);registry.set(next.id,next);let cycle=detectSnippetCycle(next.tree,next.id,registry);if(cycle)return yield*$(err(snippetCycle(next.id,cycle)))}let persisted=await persistSnippet(ctx.folder,next.id,next);return ctx.broadcast({type:"snippet-changed",snippetId:next.id}),{snippet:persisted}})}async function updateSnippetArgs(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve9(next.tree,args.path,args.screenId)),node=yield*$(getNode(next.tree,resolved,args.screenId));if(!isSnippetInstance(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(resolved)} is not a snippet instance.`,resolved)));let merged={...node.args??{}};for(let[k,v]of Object.entries(args.argPatch))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete node.args;else node.args=merged;if(args.extraClassName!==void 0)if(args.extraClassName===null||args.extraClassName.trim()==="")delete node.$extraClassName;else node.$extraClassName=args.extraClassName.trim();return yield*$(await commitScreen(ctx.folder,args.screenId,next,args.gesture)),broadcastTreeChange(ctx,args.screenId),{path:resolved}})}function addSnippet2(ctx,args){return tracked(ctx,"add_snippet",(v)=>({snippetId:v.snippetId}),()=>withSnippetLock(ctx.folder,args.id??args.name,()=>addSnippet(ctx,args)))}function updateSnippet2(ctx,args){return tracked(ctx,"update_snippet",{snippetId:args.snippetId},()=>withSnippetLock(ctx.folder,args.snippetId,()=>updateSnippet(ctx,args)))}function removeSnippet2(ctx,args){return tracked(ctx,"remove_snippet",{snippetId:args.snippetId},()=>withSnippetLock(ctx.folder,args.snippetId,()=>removeSnippet(ctx,args)))}function instantiateSnippet2(ctx,args){return tracked(ctx,"instantiate_snippet",(v)=>({screenId:args.screenId,path:v.path,snippetId:args.snippetId}),()=>withScreenLock(ctx.folder,args.screenId,()=>instantiateSnippet(ctx,args)))}function updateSnippetArgs2(ctx,args){return tracked(ctx,"update_snippet_args",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>updateSnippetArgs(ctx,args)))}async function addNode(ctx,args){let{screenId,parentPath,componentRef}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId));yield*$(ensureKnownComponent(ctx,componentRef,screen));let next=cloneScreen(screen),resolvedParent=yield*$(resolve9(next.tree,parentPath,screenId)),parent=yield*$(getComponentNode(next.tree,resolvedParent,screenId));if(!parent.children)parent.children=[];let idx=args.index??parent.children.length;if(idx<0||idx>parent.children.length)return yield*$(err(invalidPath(`index ${idx} out of range for parent with ${parent.children.length} children`,resolvedParent)));let newNode={$ref:componentRef,...args.id!==void 0?{$id:args.id}:{},...args.props?{props:args.props}:{},...args.children?{children:args.children}:{},...args.emitAs?{$emitAs:args.emitAs}:{}};return parent.children.splice(idx,0,newNode),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{path:[...resolvedParent,idx]}})}function channelForScreen(ctx,screen){return styleChannelOf(providerForScreen2(ctx,screen),ctx.folder.config.styling?.framework)}function validateStylePayload(channel,style){if(style===null)return ok(null);let wantsString=channel.kind==="tailwind-classname";if(wantsString&&typeof style!=="string")return err(badRequest(`\`style\` on the "${channel.kind}" channel expects a className string (got ${typeof style}).`));if(!wantsString&&(typeof style!=="object"||Array.isArray(style)))return err(badRequest(`\`style\` on the "${channel.kind}" channel expects an object of ${channel.prop} properties (got ${Array.isArray(style)?"array":typeof style}).`));return ok(null)}function applyStyleToProps(channel,props,style){let prop=channel.prop;if(style===null)return delete props[prop],props;if(typeof style==="string"){let trimmed=style.trim();if(trimmed==="")delete props[prop];else props[prop]=trimmed;return props}let current=props[prop],merged=current&&typeof current==="object"&&!Array.isArray(current)?{...current}:{};for(let[k,v]of Object.entries(style))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete props[prop];else props[prop]=merged;return props}async function updateProps(ctx,args){return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),channel=channelForScreen(ctx,screen);for(let{style}of args.patches)if(style!==void 0)yield*$(validateStylePayload(channel,style));let next=cloneScreen(screen),paths=[];for(let{path,propPatch,style}of args.patches){let resolved=yield*$(resolveWithSnippetHint(ctx,next.tree,path,args.screenId)),node=yield*$(getComponentNode(next.tree,resolved,args.screenId)),merged={...node.props??{}};for(let[k,v]of Object.entries(propPatch??{}))if(v===null)delete merged[k];else merged[k]=v;if(style!==void 0)applyStyleToProps(channel,merged,style);if(Object.keys(merged).length===0)delete node.props;else node.props=merged;paths.push(resolved)}return yield*$(await commitScreen(ctx.folder,args.screenId,next,args.gesture)),broadcastTreeChange(ctx,args.screenId),{paths}})}async function applyClasses(ctx,args){let className=args.classes.trim();return updateProps(ctx,{screenId:args.screenId,patches:[{path:args.path,propPatch:{className:className===""?null:className}}],gesture:args.gesture})}function isAncestor(ancestor,descendant){if(ancestor.length>=descendant.length)return!1;return ancestor.every((seg,i)=>descendant[i]===seg)}async function moveNode(ctx,args){let{screenId,fromPath,toParent}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),next=cloneScreen(screen),resolvedFrom=yield*$(resolve9(next.tree,fromPath,screenId)),resolvedTo=yield*$(resolve9(next.tree,toParent,screenId));if(resolvedFrom.length===0)return yield*$(err(invalidMove("Cannot move the screen root.")));if(isAncestor(resolvedFrom,resolvedTo)||JSON.stringify(resolvedFrom)===JSON.stringify(resolvedTo))return yield*$(err(invalidMove("Cannot move a node into itself or its descendant.")));let fromParentInfo=parentOf(resolvedFrom);if(!fromParentInfo)return yield*$(err(invalidPath("no parent",resolvedFrom)));let fromParent=pathAt(next.tree,fromParentInfo.parent);if(!fromParent||!isComponentNode(fromParent)||!fromParent.children)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolvedFrom)}`,resolvedFrom)));if(fromParentInfo.index>=fromParent.children.length)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolvedFrom)}`,resolvedFrom)));let[moved]=fromParent.children.splice(fromParentInfo.index,1);if(fromParent.children.length===0)delete fromParent.children;if(!moved)return yield*$(err(invalidPath("internal: detach lost node during move",resolvedFrom)));let adjustedToParent=resolvedTo,sameParent=JSON.stringify(fromParentInfo.parent)===JSON.stringify(resolvedTo),toIdx=args.toIndex??1/0;if(sameParent&&toIdx>fromParentInfo.index)toIdx=Math.max(0,toIdx-1);else if(resolvedTo.length>fromParentInfo.parent.length&&isAncestor(fromParentInfo.parent,resolvedTo)&&(resolvedTo[fromParentInfo.parent.length]??-1)>fromParentInfo.index)adjustedToParent=[...resolvedTo],adjustedToParent[fromParentInfo.parent.length]=adjustedToParent[fromParentInfo.parent.length]-1;let target=pathAt(next.tree,adjustedToParent);if(!target||!isComponentNode(target))return yield*$(err(invalidPath(`No parent at ${JSON.stringify(adjustedToParent)}`,adjustedToParent)));if(!target.children)target.children=[];if(toIdx=Math.min(toIdx,target.children.length),toIdx<0)toIdx=0;return target.children.splice(toIdx,0,moved),yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{newPath:[...adjustedToParent,toIdx]}})}async function overrideSnippetProps(ctx,args){return DoAsync(async function*(){if(!/^$|^\d+(\.\d+)*$|^@[a-zA-Z][a-zA-Z0-9_-]*$/.test(args.innerPath))return yield*$(err(invalidPath('innerPath must be a dotted index path like "0.2", an "@id" of a node inside the body, or "" for the root')));let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve9(next.tree,args.path,args.screenId)),node=pathAt(next.tree,resolved);if(!node||!isSnippetInstance(node))return yield*$(err(invalidPath(`Node at ${JSON.stringify(resolved)} is not a snippet instance`,resolved)));let instance=node,snippet=ctx.folder.snippets.get(instance.$snippet);if(!snippet)return yield*$(err(snippetNotFound(instance.$snippet)));if(!innerPathResolves(snippet.tree,args.innerPath))return yield*$(err(invalidPath(`innerPath "${args.innerPath}" doesn't resolve to a component inside snippet "${instance.$snippet}"`)));let overrides={...instance.$overrides??{}},merged={...overrides[args.innerPath]?.props??{}};for(let[k,v]of Object.entries(args.propPatch))if(v===null)delete merged[k];else merged[k]=v;if(Object.keys(merged).length===0)delete overrides[args.innerPath];else overrides[args.innerPath]={props:merged};if(Object.keys(overrides).length===0)delete instance.$overrides;else instance.$overrides=overrides;return yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:resolved,innerPath:args.innerPath,overrides}})}async function removeNode(ctx,args){let{screenId,path}=args;return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,screenId)),next=cloneScreen(screen),resolved=yield*$(resolve9(next.tree,path,screenId));if(resolved.length===0){if(!isComponentNode(next.tree))return yield*$(err(invalidPath("Cannot remove the screen root.",resolved)));let cleared=next.tree.children?.length??0;return delete next.tree.children,yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{removedRef:`(cleared ${cleared} child node${cleared===1?"":"s"} of the screen root \u2014 the root itself remains)`}}let parentInfo=parentOf(resolved);if(!parentInfo)return yield*$(err(invalidPath("no parent",resolved)));let parent=pathAt(next.tree,parentInfo.parent);if(!parent||!isComponentNode(parent)||!parent.children)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolved)}`,resolved)));if(parentInfo.index>=parent.children.length)return yield*$(err(invalidPath(`No node at path ${JSON.stringify(resolved)}`,resolved)));let[removed]=parent.children.splice(parentInfo.index,1);if(parent.children.length===0)delete parent.children;return yield*$(await commitScreen(ctx.folder,screenId,next)),broadcastTreeChange(ctx,screenId),{removedRef:describeRemoved(removed)}})}function describeRemoved(node){if(!node)return"?";if(isComponentNode(node))return node.$ref;if("$snippet"in node)return`@${node.$snippet}`;return`$param:${node.$param}`}async function setNodeId(ctx,args){if(args.id!==null){let parsed=NodeIdSchema.safeParse(args.id);if(!parsed.success)return{ok:!1,error:badRequest(`Invalid node id ${JSON.stringify(args.id)}`,parsed.error.issues)}}return DoAsync(async function*(){let screen=yield*$(getScreen(ctx,args.screenId)),next=cloneScreen(screen),resolved=yield*$(resolve9(next.tree,args.path,args.screenId)),node=yield*$(getNode(next.tree,resolved,args.screenId));if(!isComponentNode(node)&&!isSnippetInstance(node))return yield*$({ok:!1,error:invalidPath(`Node at ${JSON.stringify(resolved)} cannot carry an id (param refs are anonymous).`,resolved)});if(args.id===null)delete node.$id;else node.$id=args.id;return yield*$(await commitScreen(ctx.folder,args.screenId,next)),broadcastTreeChange(ctx,args.screenId),{path:resolved,id:args.id}})}function addNode2(ctx,args){return tracked(ctx,"add_node",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>addNode(ctx,args)))}function updateProps2(ctx,args){return tracked(ctx,"update_props",(v)=>({screenId:args.screenId,...v.paths[0]?{path:v.paths[0]}:{}}),()=>withScreenLock(ctx.folder,args.screenId,()=>updateProps(ctx,args)))}function moveNode2(ctx,args){return tracked(ctx,"move_node",(v)=>({screenId:args.screenId,path:v.newPath}),()=>withScreenLock(ctx.folder,args.screenId,()=>moveNode(ctx,args)))}function removeNode2(ctx,args){return tracked(ctx,"remove_node",{screenId:args.screenId},()=>withScreenLock(ctx.folder,args.screenId,()=>removeNode(ctx,args)))}function applyClasses2(ctx,args){return tracked(ctx,"apply_classes",(v)=>({screenId:args.screenId,...v.paths[0]?{path:v.paths[0]}:{}}),()=>withScreenLock(ctx.folder,args.screenId,()=>applyClasses(ctx,args)))}function setNodeId2(ctx,args){return tracked(ctx,"set_node_id",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>setNodeId(ctx,args)))}function overrideSnippetProps2(ctx,args){return tracked(ctx,"override_snippet_props",(v)=>({screenId:args.screenId,path:v.path}),()=>withScreenLock(ctx.folder,args.screenId,()=>overrideSnippetProps(ctx,args)))}function findExtensionReferences(ctx,extensionId){let out=[];function visit(node,screenId,path){if(isComponentNode(node)){if(node.$ref===extensionId)out.push({screenId,path:path.join(".")});if(node.children)for(let i=0;i<node.children.length;i++){let child=node.children[i];if(child)visit(child,screenId,[...path,i])}}}for(let[screenId,screen]of ctx.folder.screens)visit(screen.tree,screenId,[]);for(let[snippetId,snippet]of ctx.folder.snippets)visit(snippet.tree,`snippet:${snippetId}`,[]);return out}function unknownHostApp(ctx,app){if(!app)return null;let known=Object.keys(ctx.folder.config.hostApps??{});if(known.includes(app))return null;return{kind:"BadRequest",message:`app "${app}" is not a key of config.hostApps.`,hint:known.length?`Known apps: ${known.join(", ")}. Omit \`app\` to use the default host app.`:"This folder has no config.hostApps \u2014 omit `app` to use the default host app."}}async function addExtension(ctx,args){let existing=ctx.folder.config.extensions??{};if(args.id in existing)return err(extensionIdConflict(args.id));let badApp=unknownHostApp(ctx,args.app);if(badApp)return err(badApp);for(let p of args.props){let parsed=ExtensionPropDescriptorSchema.safeParse(p);if(!parsed.success)return err({kind:"InvalidExtensionProp",message:`Invalid extension prop schema: ${parsed.error.issues[0]?.message??"unknown"}`,extensionId:args.id,prop:p.name})}let extension=ExtensionSchema.parse({importPath:args.importPath,category:args.category,description:args.description,props:args.props,origin:"agent",render:args.render,app:args.app,fit:args.fit}),shadowed=args.id in ctx.defaultProvider.registry?args.id:void 0,nextConfig={...ctx.folder.config,extensions:{...existing,[args.id]:extension}};await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"add_extension",{extension:args.id});let result={id:args.id,extension};if(shadowed)result.shadowedLibraryComponent=shadowed;if(extension.render==="live"){let warning=resolveLiveImportWarning(ctx.folder.root,ctx.folder.config,args.importPath,extension.app);if(warning)result.liveResolveWarning=warning}return ok(result)}async function updateExtension(ctx,args){let existing=ctx.folder.config.extensions??{},prev=existing[args.id];if(!prev)return err(extensionNotFound(args.id));let badApp=unknownHostApp(ctx,args.patch.app);if(badApp)return err(badApp);if(args.patch.props)for(let p of args.patch.props){let parsed=ExtensionPropDescriptorSchema.safeParse(p);if(!parsed.success)return err({kind:"InvalidExtensionProp",message:`Invalid extension prop schema: ${parsed.error.issues[0]?.message??"unknown"}`,extensionId:args.id,prop:p.name})}let next=ExtensionSchema.parse({...prev,...args.patch.importPath!==void 0?{importPath:args.patch.importPath}:{},...args.patch.props!==void 0?{props:args.patch.props}:{},...args.patch.category!==void 0?{category:args.patch.category}:{},...args.patch.description!==void 0?{description:args.patch.description}:{},...args.patch.render!==void 0?{render:args.patch.render}:{},...args.patch.app!==void 0?{app:args.patch.app}:{},...args.patch.fit!==void 0?{fit:args.patch.fit}:{}}),nextConfig={...ctx.folder.config,extensions:{...existing,[args.id]:next}};await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"update_extension",{extension:args.id});let result={id:args.id,extension:next};if(next.render==="live"){let warning=resolveLiveImportWarning(ctx.folder.root,ctx.folder.config,next.importPath,next.app);if(warning)result.liveResolveWarning=warning}return ok(result)}async function removeExtension(ctx,args){let existing=ctx.folder.config.extensions??{};if(!(args.id in existing))return err(extensionNotFound(args.id));let refs=findExtensionReferences(ctx,args.id);if(refs.length>0)return err(extensionInUse(args.id,refs));let{[args.id]:_,...rest}=existing,extensions=Object.keys(rest).length>0?rest:void 0,nextConfig={...ctx.folder.config,extensions};return await persistConfig(ctx.folder,nextConfig),ctx.broadcast({type:"config-changed"}),emitActivity(ctx,"remove_extension",{extension:args.id}),ok({id:args.id})}function summarize(node,path){if(isComponentNode(node)){let props=node.props??{},children=props.children;return{path,kind:"component",ref:node.$ref,...nodeId(node)?{id:nodeId(node)}:{},...typeof props.className==="string"?{className:props.className}:{},...typeof children==="string"?{textPreview:children.slice(0,80)}:{},childCount:node.children?.length??0}}if(isSnippetInstance(node))return{path,kind:"snippet",ref:node.$snippet,...nodeId(node)?{id:nodeId(node)}:{},childCount:0};return{path,kind:"param",childCount:0}}async function findNodes(ctx,args){let screenR=getScreen(ctx,args.screenId);if(!screenR.ok)return screenR;let limit=args.limit??50,matches=[],total=0;function consider(node,path){let props=isComponentNode(node)?node.props??{}:{};if(args.ref!==void 0&&(!isComponentNode(node)||node.$ref!==args.ref))return;if(args.snippetId!==void 0&&(!isSnippetInstance(node)||node.$snippet!==args.snippetId))return;if(args.id!==void 0&&nodeId(node)!==args.id)return;if(args.classContains!==void 0){let cls=props.className;if(typeof cls!=="string"||!cls.includes(args.classContains))return}if(args.prop!==void 0){if(!(args.prop in props))return;if(args.propValue!==void 0&&props[args.prop]!==args.propValue)return}if(total+=1,matches.length<limit)matches.push(summarize(node,path))}function walk(node,path){if(consider(node,path),isComponentNode(node)&&node.children)node.children.forEach((child,i)=>{walk(child,[...path,i])})}return walk(screenR.value.tree,[]),ok({matches,total})}async function updateSnippetInstance(ctx,plan){return DoAsync(async function*(){let applied=[],path;if(plan.args)path=(yield*$(await updateSnippetArgs2(ctx,plan.args))).path,applied.push("args");if(plan.override)path=(yield*$(await overrideSnippetProps2(ctx,plan.override))).path,applied.push("override");return{path:path??[],applied}})}var invalidColor=(reason,hint)=>({kind:"InvalidColor",reason,...hint!==void 0?{hint}:{}}),invalidThemePath=(reason,hint)=>({kind:"InvalidThemePath",reason,...hint!==void 0?{hint}:{}}),unknownPreset=(presetName,hint)=>({kind:"UnknownPreset",presetName,...hint!==void 0?{hint}:{}}),themeBadRequest=(message,issues)=>({kind:"BadRequest",message,...issues!==void 0?{issues}:{}});var MUTATION_STATUS={ScreenNotFound:404,BoardNotFound:404,FrameNotFound:404,BoardGroupNotFound:404,SnippetNotFound:404,IdNotFound:404,AnnotationNotFound:404,CanvasNoteNotFound:404,ExtensionNotFound:404,UnknownComponent:422,SnippetParamMismatch:422,SnippetCycle:422,InvalidExtensionProp:422,InvalidPath:400,InvalidMove:400,ScreenIdConflict:400,ScreenIdExhausted:400,BoardIdConflict:400,BoardIdExhausted:400,FrameIdConflict:400,SnippetIdConflict:400,BadRequest:400,LastScreen:409,SnippetInUse:409,IdConflict:409,AnnotationConflict:409,ExtensionIdConflict:409,ExtensionInUse:409},THEME_STATUS={UnknownPreset:404,InvalidColor:400,InvalidThemePath:400,BadRequest:400,BulkTokensInvalid:400};function mutationToHttp(c,error){return c.json({error},MUTATION_STATUS[error.kind])}function themeToHttp(c,error){return c.json({error},THEME_STATUS[error.kind])}async function readBody(c){try{return await c.req.json()}catch{return}}function makeBoundRoute(b){return function(schema,handler){return async(c)=>{let body=await readBody(c),parsed=schema.safeParse(body);if(!parsed.success)return b.toHttp(c,b.badRequest("Request body failed validation.",parsed.error.issues));let result=await withActor({source:"canvas"},()=>handler(parsed.data,b.ctxFor()));return result.ok?c.json(result.value):b.toHttp(c,result.error)}}}function makeRoute(ctxFor){return makeBoundRoute({ctxFor,badRequest,toHttp:mutationToHttp})}function makeThemeRoute(ctxFor){return makeBoundRoute({ctxFor,badRequest:themeBadRequest,toHttp:themeToHttp})}function createAnnotationsRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.get("/:screenId",(c)=>{let ctx=ctxFor(),screenId=c.req.param("screenId"),screen=ctx.folder.screens.get(screenId),list=(ctx.folder.annotations.get(screenId)??[]).map((a)=>{let resolved=screen?resolveLocator(screen.tree,a.target.locator):null;return{...a,resolved}});return c.json({annotations:list})}),r.post("/add",route(AddAnnotationBody,(a,ctx)=>addAnnotation2(ctx,a))),r.post("/update",route(UpdateAnnotationBody,(a,ctx)=>updateAnnotation2(ctx,a))),r.post("/remove",route(RemoveAnnotationBody,(a,ctx)=>removeAnnotation2(ctx,a))),r}function createNotesRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.get("/:boardId",(c)=>{let ctx=ctxFor(),boardId=c.req.param("boardId"),list=(ctx.folder.notes.get(boardId)??[]).map((note)=>{if(!note.attachment)return note;let screen=ctx.folder.screens.get(note.attachment.screenId),resolved=screen?resolveLocator(screen.tree,note.attachment.locator):null;return{...note,resolved}});return c.json({notes:list})}),r.post("/add",route(AddNoteBody,(a,ctx)=>addNote2(ctx,a))),r.post("/update",route(UpdateNoteBody,(a,ctx)=>updateNote2(ctx,a))),r.post("/remove",route(RemoveNoteBody,(a,ctx)=>removeNote2(ctx,a))),r}function createMutateRouter(ctxFor){let r=new Hono2,route=makeRoute(ctxFor);return r.post("/update_props",route(UpdatePropsBody,(a,ctx)=>updateProps2(ctx,a))),r.post("/apply_classes",route(ApplyClassesBody,(a,ctx)=>applyClasses2(ctx,a))),r.post("/set_node_id",route(SetNodeIdBody,(a,ctx)=>setNodeId2(ctx,a))),r.post("/add_board",route(AddBoardBody,(a,ctx)=>addBoard2(ctx,a))),r.post("/update_board",route(UpdateBoardBody,(a,ctx)=>updateBoard2(ctx,a))),r.post("/remove_board",route(RemoveBoardBody,(a,ctx)=>removeBoard2(ctx,a))),r.post("/reorder_boards",route(ReorderBoardsBody,(a,ctx)=>reorderBoards2(ctx,a))),r.post("/add_board_group",route(AddBoardGroupBody,(a,ctx)=>addBoardGroup2(ctx,a))),r.post("/update_board_group",route(UpdateBoardGroupBody,(a,ctx)=>updateBoardGroup2(ctx,a))),r.post("/remove_board_group",route(RemoveBoardGroupBody,(a,ctx)=>removeBoardGroup2(ctx,a))),r.post("/reorder_board_groups",route(ReorderBoardGroupsBody,(a,ctx)=>reorderBoardGroups2(ctx,a))),r.post("/update_viewport_presets",route(UpdateViewportPresetsBody,(a,ctx)=>updateViewportPresets2(ctx,a))),r.post("/update_defaults",route(UpdateDefaultsBody,(a,ctx)=>updateDefaults2(ctx,a))),r.post("/update_design_name",route(UpdateDesignNameBody,(a,ctx)=>updateDesignName2(ctx,a))),r.post("/update_codegen",route(UpdateCodegenBody,(a,ctx)=>updateCodegen2(ctx,a))),r.post("/update_feedback",route(UpdateFeedbackBody,(a,ctx)=>updateFeedback2(ctx,a))),r.post("/add_frame",route(AddFrameBody,(a,ctx)=>addFrame2(ctx,a))),r.post("/update_frame",route(UpdateFrameBody,(a,ctx)=>updateFrames2(ctx,a))),r.post("/remove_frame",route(RemoveFrameBody,(a,ctx)=>removeFrame2(ctx,a))),r.post("/move_frame",route(MoveFrameBody,(a,ctx)=>moveFrame2(ctx,a))),r.post("/update_snippet",route(UpdateSnippetBody,(a,ctx)=>updateSnippet2(ctx,a))),r.post("/update_snippet_args",route(UpdateSnippetArgsBody,(a,ctx)=>updateSnippetArgs2(ctx,a))),r}function createPreflightRouter(ctxFor){let r=new Hono2;return r.get("/",(c)=>{let folder=ctxFor().folder,source={folder,providers:ctxFor().providers,defaultProvider:ctxFor().defaultProvider},kind=c.req.query("kind"),id=c.req.query("id");if(kind!==void 0||id!==void 0){if(kind!=="frame"&&kind!=="board"&&kind!=="screen")return c.json({error:"kind must be frame, board, or screen"},400);if(!id)return c.json({error:"id is required with kind"},400);let screens=screensForExportTarget(folder,kind,id);if(screens.length===0)return c.json({error:`no ${kind} with id "${id}"`},404);return c.json({failures:preflightScreens(source,screens)})}let boards=c.req.query("boards"),screens=boards===void 0||boards===""?[...folder.screens.values()]:screensForBoards(folder,boards.split(","));return c.json({failures:preflightScreens(source,screens)})}),r}function createPublishRouter(runner){let app=new Hono2,noPublisher="this server cannot publish \u2014 run the canvas through the velloo CLI",unavailable={error:"this server cannot publish \u2014 run the canvas through the velloo CLI"};app.get("/status",(c)=>{if(!runner)return c.json({state:"unavailable"});return c.json(runner.state())}),app.get("/targets",async(c)=>{let closed=(access)=>c.json({ready:!1,access,teams:[],slots:[]});if(!runner)return closed("signed-out");let access=await runner.access();if(access.state!=="ready")return closed(access.state);let[teams,destinationResult]=await Promise.all([runner.teams().catch(()=>[]),runner.destinations().then((value)=>({value})).catch((error)=>({error:error instanceof Error?error.message:String(error),signInRequired:asSignInRequired(error)}))]);if("error"in destinationResult){if(destinationResult.signInRequired)return closed(destinationResult.signInRequired);return c.json({ready:!0,access:"ready",teams,slots:[],destinationError:destinationResult.error})}return c.json({ready:!0,access:"ready",teams,...destinationResult.value})}),app.post("/",async(c)=>{if(!runner)return c.json(unavailable,503);let body=await c.req.json().catch(()=>({})),boardIds=Array.isArray(body.boardIds)?body.boardIds.filter((id)=>typeof id==="string"):[],visibility=body.visibility==="private"?"private":"public",title=typeof body.title==="string"?body.title:void 0,teamId=typeof body.teamId==="string"&&body.teamId?body.teamId:void 0;if(typeof body.password==="string"&&body.password.length<3)return c.json({error:"password must be at least 3 characters"},400);let password=typeof body.password==="string"?body.password:void 0,rawDestination=body.destination,destination=rawDestination?.mode==="new"?{mode:"new"}:rawDestination?.mode==="update"&&typeof rawDestination.slug==="string"&&(typeof rawDestination.expectedVersionId==="string"||rawDestination.expectedVersionId===null)?{mode:"update",slug:rawDestination.slug,expectedVersionId:rawDestination.expectedVersionId}:null;if(!destination)return c.json({error:"choose a publish destination"},400);let started=runner.start({boardIds,visibility,destination,...title?{title}:{},...password?{password}:{},...teamId?{teamId}:{}});if(!started)return c.json({error:"a publish is already running for this folder"},409);return c.json(started,202)});let cloudFail=(c,error)=>{let message=error instanceof Error?error.message:String(error),kind=asSignInRequired(error)?"LoggedOut":"HttpFailure";return c.json({error:{kind,message}},502)},noPublisherFail=(c)=>c.json({error:{kind:"HttpFailure",message:"this server cannot publish \u2014 run the canvas through the velloo CLI"}},503);return app.get("/published",async(c)=>{if(!runner)return noPublisherFail(c);try{return c.json({boards:await runner.published()})}catch(error){return cloudFail(c,error)}}),app.delete("/published/:slug",async(c)=>{if(!runner)return noPublisherFail(c);try{return await runner.unpublish(c.req.param("slug")),c.json({ok:!0})}catch(error){return cloudFail(c,error)}}),app.post("/reset",(c)=>(runner?.reset(),c.json({ok:!0}))),app}import{randomBytes}from"crypto";var SHOWCASES={Button:()=>({$ref:"Button",props:{children:"Button"}}),Badge:()=>({$ref:"Badge",props:{children:"Badge"}}),Heading:()=>({$ref:"Heading",props:{level:2,children:"Heading"}}),Text:()=>({$ref:"Text",props:{children:"Body text"}}),Label:()=>({$ref:"Card",props:{className:"flex flex-row items-center gap-2 ring-0 shadow-none bg-transparent p-0"},children:[{$ref:"Checkbox",props:{id:"showcase-label-cb",defaultChecked:!0}},{$ref:"Label",props:{htmlFor:"showcase-label-cb",children:"Accept terms"}}]}),Input:()=>({$ref:"Input",props:{placeholder:"you@company.com"}}),Textarea:()=>({$ref:"Textarea",props:{placeholder:"Write a message\u2026"}}),Switch:()=>({$ref:"Switch",props:{defaultChecked:!0}}),Checkbox:()=>({$ref:"Checkbox",props:{defaultChecked:!0}}),Slider:()=>({$ref:"Slider",props:{defaultValue:[40],className:"w-40"}}),Progress:()=>({$ref:"Progress",props:{value:66,className:"w-48"}}),Skeleton:()=>({$ref:"Skeleton",props:{className:"h-6 w-40"}}),Separator:()=>({$ref:"Separator",props:{className:"w-40"}}),Toggle:()=>({$ref:"Toggle",props:{variant:"outline",defaultPressed:!0,children:"Bold"}}),Calendar:()=>({$ref:"Calendar"}),Icon:()=>({$ref:"Icon",props:{name:"Sparkles",size:24}}),Placeholder:()=>({$ref:"Placeholder",props:{kind:"image",aspect:"16/9"}}),Divider:()=>({$ref:"Divider",props:{label:"or"}}),Avatar:()=>({$ref:"Avatar",children:[{$ref:"AvatarFallback",props:{children:"AB"}}]}),Alert:()=>({$ref:"Alert",children:[{$ref:"AlertTitle",props:{children:"Heads up!"}},{$ref:"AlertDescription",props:{children:"You can drop this component on a board."}}]}),Card:()=>({$ref:"Card",props:{className:"p-4 flex flex-col gap-1"},children:[{$ref:"Text",props:{className:"text-sm font-medium",children:"Card title"}},{$ref:"Text",props:{className:"text-xs text-muted-foreground",children:"A flexible container for content."}}]}),Tabs:()=>({$ref:"Tabs",props:{defaultValue:"overview"},children:[{$ref:"TabsList",children:[{$ref:"TabsTrigger",props:{value:"overview",children:"Overview"}},{$ref:"TabsTrigger",props:{value:"activity",children:"Activity"}},{$ref:"TabsTrigger",props:{value:"settings",children:"Settings"}}]}]}),Breadcrumb:()=>({$ref:"Breadcrumb",children:[{$ref:"BreadcrumbList",children:[{$ref:"BreadcrumbItem",children:[{$ref:"BreadcrumbLink",props:{children:"Home"}}]},{$ref:"BreadcrumbSeparator"},{$ref:"BreadcrumbItem",children:[{$ref:"BreadcrumbPage",props:{children:"Library"}}]}]}]}),Pagination:()=>({$ref:"Pagination",children:[{$ref:"PaginationContent",children:[{$ref:"PaginationPrevious"},{$ref:"PaginationItem",children:[{$ref:"PaginationLink",props:{children:"1"}}]},{$ref:"PaginationItem",children:[{$ref:"PaginationLink",props:{isActive:!0,children:"2"}}]},{$ref:"PaginationNext"}]}]}),Accordion:()=>({$ref:"Accordion",props:{type:"single",collapsible:!0,defaultValue:"a"},children:[{$ref:"AccordionItem",props:{value:"a"},children:[{$ref:"AccordionTrigger",props:{children:"What is Velloo?"}},{$ref:"AccordionContent",props:{children:"A local design canvas for solo devs."}}]}]}),RadioGroup:()=>({$ref:"RadioGroup",props:{defaultValue:"a"},children:[{$ref:"Card",props:{className:"flex items-center gap-2 ring-0 shadow-none p-0 bg-transparent"},children:[{$ref:"RadioGroupItem",props:{value:"a",id:"showcase-r1"}},{$ref:"Label",props:{htmlFor:"showcase-r1",children:"Option"}}]}]}),ToggleGroup:()=>({$ref:"ToggleGroup",props:{type:"single",defaultValue:"left"},children:[{$ref:"ToggleGroupItem",props:{value:"left",children:"Left"}},{$ref:"ToggleGroupItem",props:{value:"center",children:"Center"}},{$ref:"ToggleGroupItem",props:{value:"right",children:"Right"}}]}),Select:()=>({$ref:"Select",children:[{$ref:"SelectTrigger",props:{className:"w-40"},children:[{$ref:"SelectValue",props:{placeholder:"Choose\u2026"}}]}]}),Toaster:()=>({$ref:"Toaster",props:{position:"bottom-right"}}),Dialog:()=>({$ref:"Dialog",children:[{$ref:"DialogContent",props:{className:"w-72"},children:[{$ref:"DialogHeader",children:[{$ref:"DialogTitle",props:{children:"Confirm change"}},{$ref:"DialogDescription",props:{children:"Modal dialog content shown when triggered."}}]},{$ref:"DialogFooter",children:[{$ref:"Button",props:{variant:"outline",size:"sm",children:"Cancel"}},{$ref:"Button",props:{size:"sm",children:"Save"}}]}]}]}),AlertDialog:()=>({$ref:"AlertDialog",children:[{$ref:"AlertDialogContent",props:{className:"w-72"},children:[{$ref:"AlertDialogHeader",children:[{$ref:"AlertDialogTitle",props:{children:"Delete file?"}},{$ref:"AlertDialogDescription",props:{children:"This action can't be undone."}}]},{$ref:"AlertDialogFooter",children:[{$ref:"AlertDialogCancel",props:{size:"sm",children:"Cancel"}},{$ref:"AlertDialogAction",props:{variant:"destructive",size:"sm",children:"Delete"}}]}]}]}),Popover:()=>({$ref:"Popover",children:[{$ref:"PopoverContent",props:{className:"w-56"},children:[{$ref:"PopoverHeader",children:[{$ref:"PopoverTitle",props:{children:"Popover content"}},{$ref:"PopoverDescription",props:{children:"Anchored to a trigger."}}]}]}]}),DropdownMenu:()=>({$ref:"DropdownMenu",children:[{$ref:"DropdownMenuContent",props:{className:"w-44"},children:[{$ref:"DropdownMenuItem",props:{children:"Profile"}},{$ref:"DropdownMenuItem",props:{children:"Settings"}},{$ref:"DropdownMenuSeparator"},{$ref:"DropdownMenuItem",props:{variant:"destructive",children:"Log out"}}]}]}),Tooltip:()=>({$ref:"Tooltip",children:[{$ref:"TooltipContent",props:{children:"Helpful hint"}}]}),Sheet:()=>({$ref:"Sheet",children:[{$ref:"SheetContent",props:{side:"right",className:"h-56 w-64 rounded-lg p-4"},children:[{$ref:"SheetHeader",children:[{$ref:"SheetTitle",props:{children:"Slide-in panel"}},{$ref:"SheetDescription",props:{children:"Drawer mounted on a screen edge."}}]}]}]}),Collapsible:()=>({$ref:"Collapsible",props:{defaultOpen:!0},children:[{$ref:"CollapsibleTrigger",children:[{$ref:"Button",props:{variant:"outline",size:"sm",children:"Toggle"}}]},{$ref:"CollapsibleContent",children:[{$ref:"Card",props:{className:"mt-2 p-2 ring-0 shadow-none bg-muted text-xs"},children:[{$ref:"Text",props:{className:"text-xs",children:"Hidden content revealed."}}]}]}]}),Carousel:()=>({$ref:"Carousel",props:{className:"w-56"},children:[{$ref:"CarouselContent",children:[{$ref:"CarouselItem",children:[{$ref:"Card",props:{className:"p-6 bg-primary text-primary-foreground"},children:[{$ref:"Text",props:{className:"text-sm font-medium",children:"Slide 1"}}]}]}]},{$ref:"CarouselPrevious"},{$ref:"CarouselNext"}]}),ScrollArea:()=>({$ref:"ScrollArea",props:{className:"h-24 w-48 rounded-md border border-border p-2"},children:[{$ref:"Card",props:{className:"flex flex-col gap-1 ring-0 shadow-none bg-transparent p-0"},children:Array.from({length:6},(_,i)=>({$ref:"Text",props:{className:"text-xs",children:`Item ${i+1}`}}))}]}),Layer:()=>({$ref:"Card",props:{className:"relative h-24 w-40 bg-muted rounded-md overflow-hidden"},children:[{$ref:"Layer",props:{top:"1rem",left:"1rem"},children:[{$ref:"Card",props:{className:"px-2 py-0.5 rounded-full bg-primary text-primary-foreground ring-0 shadow-none"},children:[{$ref:"Text",props:{className:"text-xs font-medium",children:"Layer"}}]}]}]}),Gradient:()=>({$ref:"Gradient",props:{preset:"sunset",className:"h-20 w-full rounded-md"}}),Image:()=>({$ref:"Image",props:{src:"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 9'><rect width='16' height='9' fill='%23ddd'/></svg>",aspect:"16/9",fill:!1}}),SVG:()=>({$ref:"SVG",props:{viewBox:"0 0 24 24",content:"<circle cx='12' cy='12' r='8' fill='currentColor' opacity='0.2'/><circle cx='12' cy='12' r='4' fill='currentColor'/>",className:"h-12 w-12 text-primary"}}),Chart:()=>({$ref:"Chart",props:{kind:"line",data:[{x:0,y:4},{x:1,y:6},{x:2,y:5},{x:3,y:9},{x:4,y:7},{x:5,y:12}],className:"h-20 w-full"}})};function buildShowcaseTree(componentRef,propOverrides){let isIconSize=typeof propOverrides?.size==="string"&&propOverrides.size.startsWith("icon"),builder=SHOWCASES[componentRef];if(builder){let tree=builder();if(propOverrides&&Object.keys(propOverrides).length>0)tree.props={...tree.props??{},...propOverrides};if(isIconSize&&componentRef==="Button")delete tree.props?.children,tree.children=[{$ref:"Icon",props:{name:"Plus",size:14}}];return tree}return{$ref:componentRef,props:{children:componentRef,...propOverrides??{}}}}var DARK={bg:"#0D0C0A",fg:"#F7F2E6",muted:"#A59C8D",border:"#26221C",code:"#15130E"},LIGHT={bg:"#F7F2E6",fg:"#141210",muted:"#6F665A",border:"#E2D8C4",code:"#FFFDF7"};function escapeHtml(value){return value.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function renderErrorDocument(page){let c=page.dark?DARK:LIGHT;return`<!doctype html>
|
|
176
176
|
<html lang="en"><head><meta charset="utf-8">
|
|
177
177
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
178
178
|
<title>${escapeHtml(page.title)} \xB7 ${escapeHtml(page.screenName)}</title>
|
|
@@ -216,8 +216,8 @@ ${LOADER_MERGE_BODY}`}function realPath(path){try{return realpathSync2(path)}cat
|
|
|
216
216
|
<pre>${escapeHtml(page.detail)}</pre>
|
|
217
217
|
<p class="hint">${escapeHtml(page.hint)}</p>
|
|
218
218
|
</main></body></html>`}function renderCsp(nonce){return`script-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self'`}function createRenderRouter(ctxFor,jit,bundler,canvasBundler){let r=new Hono2,liveBundleUrl=(ctx)=>Object.keys(liveExtensions(ctx.folder.config.extensions)).length>0?`/api/live/bundle.js?v=${bundler.version}`:void 0,canvasBundleFor=async(ctx,screen,theme,dark)=>{let provider=providerForScreen2(ctx,screen);if(!provider.canvasBundleSpec)return;let tree=serializeTree(screen.tree,{snippets:ctx.folder.snippets}),refs=collectSerializedRefs(tree);if(refs.length===0)return;let extensionIds=new Set(Object.keys(ctx.folder.config.extensions??{}));if(refs.some((ref)=>extensionIds.has(ref)))return;let libraryId=libraryIdForScreen(ctx,screen);if(!(await canvasBundler.build(libraryId,refs)).usable)return;return{url:`/api/canvas/bundle.js?v=${canvasBundler.version}&lib=${encodeURIComponent(libraryId)}&refs=${encodeURIComponent(refs.join(","))}`,themeOptions:provider.themeToNative?.(theme,dark)??null}},parseViewport=(c,dw,dh)=>({w:Number(c.req.query("w"))||dw,h:Number(c.req.query("h"))||dh}),renderPreview=async(c,opts)=>{let{ctx,screen,viewport}=opts,f=ctx.folder,owner=opts.libraryOf??screen,dark=c.req.query("mode")==="dark",nonce=randomBytes(16).toString("base64"),headers={"Content-Type":"text/html; charset=utf-8","Content-Security-Policy":renderCsp(nonce)};try{let snapshotCss=await jit.build(),theme=themeByName(f,c.req.query("theme")),canvasBundle=opts.withBundles?await canvasBundleFor(ctx,screen,theme,dark):void 0,{html}=await renderScreen(screen,theme,{viewport,snapshotCss,registry:registryForScreen2(ctx,owner),renderPass:renderPassForScreen2(ctx,owner,theme,dark),snippets:f.snippets,customCss:f.customCss,dark,...opts.withBundles?{liveBundleUrl:liveBundleUrl(ctx)}:{},...canvasBundle?{canvasBundle}:{},...opts.selectionRing?{selectionRing:!0}:{},scriptNonce:nonce});return c.body(html,200,headers)}catch(err){let page=err instanceof UnknownComponentError?{title:"Unknown component",lede:`Nothing in this screen's library is registered as "${err.ref}", so the screen has no tree to draw.`,detail:err.message,hint:"Check the spelling against list_components, or register it as an extension."}:err instanceof RenderGuardLimitError?{title:"Too many broken components",lede:"Each of these could have been replaced with a placeholder on its own, but a screen with this many is not worth drawing around. Every other screen is unaffected.",detail:err.failures.map((failure)=>`${failure.componentId}: ${failure.reason}`).join(`
|
|
219
|
-
`),hint:"Most of these are parts placed without their parent. Fix a few and the rest of the screen draws again."}:{title:"This screen didn't render",lede:"A component threw an error the canvas could not pin on a single node, so it could not stand in for it and draw the rest. Every other screen is unaffected.",detail:err instanceof Error?err.message:String(err),hint:"The message above is the component's own. It usually names the prop or the parent it needs."};return c.body(renderErrorDocument({...page,screenName:screen.name,dark}),err instanceof UnknownComponentError?422:500,headers)}};return r.get("/snippet/:snippetId",async(c)=>{let ctx=ctxFor(),snippet=ctx.folder.snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);let viewport=parseViewport(c,480,320),args={};for(let p of snippet.params){if(p.default!==void 0)continue;args[p.name]=snippetParamPlaceholder(p)}let snippetInstance={$snippet:snippet.id,...Object.keys(args).length>0?{args}:{}},screen={id:`${snippet.id}__preview`,name:`${snippet.name} preview`,tree:{$ref:"Card",props:{className:"min-h-screen w-full flex items-center justify-center p-6 ring-0 shadow-none bg-transparent rounded-none","data-velloo-snippet-preview":"true"},children:[snippetInstance]}};return renderPreview(c,{ctx,screen,viewport,libraryOf:snippet})}),r.get("/snippet-body/:snippetId",async(c)=>{let ctx=ctxFor(),snippet=ctx.folder.snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);let viewport=parseViewport(c,480,320),paramDefaults={};for(let p of snippet.params)paramDefaults[p.name]=snippetParamPlaceholder(p);let tree=resolveSnippetBodyForEdit(snippet.tree,paramDefaults,snippet.id),screen={id:`${snippet.id}__body`,name:`${snippet.name} body`,tree};return renderPreview(c,{ctx,screen,viewport,libraryOf:snippet,selectionRing:!0})}),r.get("/component/:componentId",async(c)=>{let ctx=ctxFor(),componentId=c.req.param("componentId"),viewport=parseViewport(c,480,200),rawProps=c.req.query("props"),propOverrides;if(rawProps)try{let parsed=JSON.parse(rawProps);if(parsed&&typeof parsed==="object"&&!Array.isArray(parsed))propOverrides=parsed}catch{return c.json({error:"invalid props JSON"},400)}let inner=buildShowcaseTree(componentId,propOverrides),tree={$ref:"Card",props:{className:"min-h-screen w-full flex items-center justify-center p-4 ring-0 shadow-none bg-transparent rounded-none"},children:[inner]},screen={id:`${componentId}__preview`,name:`${componentId} preview`,tree};return renderPreview(c,{ctx,screen,viewport})}),r.get("/:screenId",async(c)=>{let ctx=ctxFor(),f=ctx.folder,screen=f.screens.get(c.req.param("screenId"));if(!screen)return c.json({error:"screen not found"},404);let preset=f.config.viewportPresets[0]??{name:"default",w:1440,h:900},viewport=parseViewport(c,preset.w,preset.h);return renderPreview(c,{ctx,screen,viewport,withBundles:!0})}),r}var TEXT_PROPS=["children","label","placeholder","title","alt"],EXCERPT_WINDOW=120;function emptySearchResult(query){return{query,boards:[],screens:[],text:[],textTotal:0}}function nameRank(name,id,q,archived=!1){let prefix=name.toLowerCase().startsWith(q)||id.toLowerCase().startsWith(q)?0:1;return(archived?2:0)+prefix}function nameMatches(name,id,q){return name.toLowerCase().includes(q)||id.toLowerCase().includes(q)}function excerptAround(value,index,queryLength){if(value.length<=EXCERPT_WINDOW)return{excerpt:value,matchStart:index,matchEnd:index+queryLength};let start=Math.max(0,index-40),end=Math.min(value.length,start+EXCERPT_WINDOW),prefix=start>0?"\u2026":"",suffix=end<value.length?"\u2026":"",excerpt=prefix+value.slice(start,end)+suffix,matchStart=prefix.length+(index-start);return{excerpt,matchStart,matchEnd:Math.min(excerpt.length,matchStart+queryLength)}}function searchFolder(folder,rawQuery,textLimit=20){let query=rawQuery.trim(),q=query.toLowerCase();if(q==="")return emptySearchResult(query);let boardsInOrder=orderedBoards(folder),boards=boardsInOrder.filter(([id,b])=>nameMatches(b.name,id,q)).map(([id,b])=>({id,name:b.name,frameCount:b.frames.length,archived:isArchived(b)})).sort((a,b)=>nameRank(a.name,a.id,q,a.archived)-nameRank(b.name,b.id,q,b.archived)),hostBoards=new Map;for(let[boardId,board]of boardsInOrder)for(let frame of board.frames){let hosts=hostBoards.get(frame.screen)??[];if(!hosts.some((h)=>h.id===boardId))hosts.push({id:boardId,name:board.name,archived:isArchived(board)});hostBoards.set(frame.screen,hosts)}for(let hosts of hostBoards.values())hosts.sort((a,b)=>Number(a.archived)-Number(b.archived));let screens=[...folder.screens.entries()].filter(([id,s])=>nameMatches(s.name,id,q)).map(([id,s])=>({id,name:s.name,boards:hostBoards.get(id)??[]})).sort((a,b)=>nameRank(a.name,a.id,q)-nameRank(b.name,b.id,q)),text=[],textTotal=0;function consider(screenId,screenName,node,path,prop,value){let index=value.toLowerCase().indexOf(q);if(index===-1)return!1;if(textTotal+=1,text.length<textLimit){let isSnippet=isSnippetInstance(node);text.push({screenId,screenName,board:hostBoards.get(screenId)?.[0]??null,path,kind:isSnippet?"snippet":"component",ref:isSnippet?node.$snippet:isComponentNode(node)?node.$ref:"",...nodeId(node)?{nodeId:nodeId(node)}:{},prop,...excerptAround(value,index,q.length)})}return!0}function walk(screenId,screenName,node,path){if(isComponentNode(node)){let props=node.props??{};for(let prop of TEXT_PROPS){let value=props[prop];if(typeof value==="string"&&consider(screenId,screenName,node,path,prop,value))break}node.children?.forEach((child,i)=>{walk(screenId,screenName,child,[...path,i])});return}if(isSnippetInstance(node)){for(let[arg,value]of Object.entries(node.args??{}))if(typeof value==="string"&&consider(screenId,screenName,node,path,arg,value))break}}for(let[screenId,screen]of folder.screens)walk(screenId,screen.name,screen.tree,[]);return{query,boards,screens,text,textTotal}}function createSearchRouter(folder){let r=new Hono2;return r.get("/",(c)=>{let q=c.req.query("q")??"",limitRaw=Number(c.req.query("limit")??"20"),limit=Number.isFinite(limitRaw)?Math.min(100,Math.max(1,Math.floor(limitRaw))):20;if(q.trim()==="")return c.json(emptySearchResult(q));return c.json(searchFolder(folder(),q,limit))}),r}import{rm as rm3}from"fs/promises";import{join as join24}from"path";var baseTypography={fontFamily:{sans:"Inter, ui-sans-serif, system-ui, sans-serif",mono:"ui-monospace, SFMono-Regular, monospace"},typesets:{default:{size:"1em",leading:1.75,flow:"1.25em",fontBody:"sans",fontMono:"mono"},docs:{size:15,leading:1.75,flow:"1.5em"},chat:{leading:1.6,flow:"1em"},compact:{size:14,leading:1.6,flow:"1em"},reading:{size:18,leading:1.9,flow:"2em"}}},baseSpacing={0:0,1:4,2:8,3:12,4:16,6:24,8:32,12:48,16:64},baseRadius={sm:4,md:6,lg:8,xl:12,full:9999},PRESETS={"default-light":{name:"default-light",colors:{background:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)",primary:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},secondary:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.205 0 0)"},muted:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.556 0 0)"},accent:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.205 0 0)"},destructive:{DEFAULT:"oklch(0.577 0.245 27.325)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},popover:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},border:"oklch(0.922 0 0)",input:"oklch(0.922 0 0)",ring:"oklch(0.708 0 0)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},"default-dark":{name:"default-dark",colors:{background:"oklch(0.145 0 0)",foreground:"oklch(0.985 0 0)",primary:{DEFAULT:"oklch(0.985 0 0)",foreground:"oklch(0.205 0 0)"},secondary:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.985 0 0)"},muted:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.708 0 0)"},accent:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.985 0 0)"},destructive:{DEFAULT:"oklch(0.396 0.141 25.723)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},popover:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},border:"oklch(0.269 0 0)",input:"oklch(0.269 0 0)",ring:"oklch(0.439 0 0)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},violet:{name:"violet",colors:{background:"oklch(0.985 0.012 290)",foreground:"oklch(0.22 0.04 290)",primary:{DEFAULT:"oklch(0.55 0.22 290)",foreground:"oklch(0.985 0.01 290)"},secondary:{DEFAULT:"oklch(0.94 0.04 290)",foreground:"oklch(0.222 0.05 290)"},muted:{DEFAULT:"oklch(0.95 0.03 290)",foreground:"oklch(0.5 0.05 290)"},accent:{DEFAULT:"oklch(0.9 0.08 290)",foreground:"oklch(0.222 0.05 290)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 290)",foreground:"oklch(0.22 0.04 290)"},popover:{DEFAULT:"oklch(0.99 0.01 290)",foreground:"oklch(0.22 0.04 290)"},border:"oklch(0.9 0.04 290)",input:"oklch(0.9 0.04 290)",ring:"oklch(0.55 0.22 290)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},emerald:{name:"emerald",colors:{background:"oklch(0.985 0.012 155)",foreground:"oklch(0.22 0.04 155)",primary:{DEFAULT:"oklch(0.5 0.18 155)",foreground:"oklch(0.985 0.01 155)"},secondary:{DEFAULT:"oklch(0.94 0.04 155)",foreground:"oklch(0.222 0.05 155)"},muted:{DEFAULT:"oklch(0.95 0.03 155)",foreground:"oklch(0.5 0.05 155)"},accent:{DEFAULT:"oklch(0.9 0.09 155)",foreground:"oklch(0.222 0.05 155)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 155)",foreground:"oklch(0.22 0.04 155)"},popover:{DEFAULT:"oklch(0.99 0.01 155)",foreground:"oklch(0.22 0.04 155)"},border:"oklch(0.9 0.04 155)",input:"oklch(0.9 0.04 155)",ring:"oklch(0.5 0.18 155)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},amber:{name:"amber",colors:{background:"oklch(0.97 0.025 80)",foreground:"oklch(0.22 0.05 50)",primary:{DEFAULT:"oklch(0.72 0.18 70)",foreground:"oklch(0.18 0.04 50)"},secondary:{DEFAULT:"oklch(0.92 0.06 75)",foreground:"oklch(0.22 0.05 50)"},muted:{DEFAULT:"oklch(0.93 0.04 75)",foreground:"oklch(0.5 0.05 50)"},accent:{DEFAULT:"oklch(0.88 0.12 75)",foreground:"oklch(0.22 0.05 50)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.02 80)",foreground:"oklch(0.22 0.05 50)"},popover:{DEFAULT:"oklch(0.99 0.02 80)",foreground:"oklch(0.22 0.05 50)"},border:"oklch(0.88 0.05 75)",input:"oklch(0.88 0.05 75)",ring:"oklch(0.72 0.18 70)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},rose:{name:"rose",colors:{background:"oklch(0.985 0.012 17)",foreground:"oklch(0.22 0.04 17)",primary:{DEFAULT:"oklch(0.585 0.197 17)",foreground:"oklch(0.99 0.005 17)"},secondary:{DEFAULT:"oklch(0.94 0.04 17)",foreground:"oklch(0.222 0.04 17)"},muted:{DEFAULT:"oklch(0.95 0.03 17)",foreground:"oklch(0.5 0.04 17)"},accent:{DEFAULT:"oklch(0.9 0.07 17)",foreground:"oklch(0.222 0.04 17)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 17)",foreground:"oklch(0.22 0.04 17)"},popover:{DEFAULT:"oklch(0.99 0.01 17)",foreground:"oklch(0.22 0.04 17)"},border:"oklch(0.9 0.04 17)",input:"oklch(0.9 0.04 17)",ring:"oklch(0.585 0.197 17)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},indigo:{name:"indigo",colors:{background:"oklch(0.99 0.005 265)",foreground:"oklch(0.22 0.04 265)",primary:{DEFAULT:"oklch(0.52 0.18 265)",foreground:"oklch(0.985 0.01 265)"},secondary:{DEFAULT:"oklch(0.95 0.025 265)",foreground:"oklch(0.22 0.04 265)"},muted:{DEFAULT:"oklch(0.96 0.02 265)",foreground:"oklch(0.5 0.04 265)"},accent:{DEFAULT:"oklch(0.9 0.08 265)",foreground:"oklch(0.22 0.04 265)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 265)",foreground:"oklch(0.22 0.04 265)"},popover:{DEFAULT:"oklch(0.99 0.01 265)",foreground:"oklch(0.22 0.04 265)"},border:"oklch(0.9 0.04 265)",input:"oklch(0.9 0.04 265)",ring:"oklch(0.52 0.18 265)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},ocean:{name:"ocean",colors:{background:"oklch(0.985 0.013 220)",foreground:"oklch(0.21 0.04 220)",primary:{DEFAULT:"oklch(0.55 0.14 220)",foreground:"oklch(0.985 0.01 220)"},secondary:{DEFAULT:"oklch(0.94 0.03 220)",foreground:"oklch(0.22 0.04 220)"},muted:{DEFAULT:"oklch(0.95 0.025 220)",foreground:"oklch(0.5 0.04 220)"},accent:{DEFAULT:"oklch(0.86 0.1 220)",foreground:"oklch(0.21 0.05 220)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 220)",foreground:"oklch(0.21 0.04 220)"},popover:{DEFAULT:"oklch(0.99 0.01 220)",foreground:"oklch(0.21 0.04 220)"},border:"oklch(0.89 0.03 220)",input:"oklch(0.89 0.03 220)",ring:"oklch(0.55 0.14 220)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},slate:{name:"slate",colors:{background:"oklch(0.985 0.005 250)",foreground:"oklch(0.205 0.02 250)",primary:{DEFAULT:"oklch(0.35 0.04 250)",foreground:"oklch(0.985 0 0)"},secondary:{DEFAULT:"oklch(0.94 0.01 250)",foreground:"oklch(0.205 0.02 250)"},muted:{DEFAULT:"oklch(0.95 0.008 250)",foreground:"oklch(0.5 0.02 250)"},accent:{DEFAULT:"oklch(0.88 0.015 250)",foreground:"oklch(0.205 0.02 250)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0 0)",foreground:"oklch(0.205 0.02 250)"},popover:{DEFAULT:"oklch(0.99 0 0)",foreground:"oklch(0.205 0.02 250)"},border:"oklch(0.9 0.01 250)",input:"oklch(0.9 0.01 250)",ring:"oklch(0.5 0.03 250)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},forest:{name:"forest",colors:{background:"oklch(0.985 0.015 145)",foreground:"oklch(0.21 0.04 145)",primary:{DEFAULT:"oklch(0.42 0.12 145)",foreground:"oklch(0.985 0.01 145)"},secondary:{DEFAULT:"oklch(0.92 0.04 145)",foreground:"oklch(0.21 0.05 145)"},muted:{DEFAULT:"oklch(0.94 0.03 145)",foreground:"oklch(0.5 0.05 145)"},accent:{DEFAULT:"oklch(0.86 0.1 145)",foreground:"oklch(0.21 0.05 145)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 145)",foreground:"oklch(0.21 0.04 145)"},popover:{DEFAULT:"oklch(0.99 0.01 145)",foreground:"oklch(0.21 0.04 145)"},border:"oklch(0.88 0.04 145)",input:"oklch(0.88 0.04 145)",ring:"oklch(0.42 0.12 145)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},sunset:{name:"sunset",colors:{background:"oklch(0.97 0.022 50)",foreground:"oklch(0.21 0.04 35)",primary:{DEFAULT:"oklch(0.62 0.2 35)",foreground:"oklch(0.985 0.01 35)"},secondary:{DEFAULT:"oklch(0.93 0.06 50)",foreground:"oklch(0.21 0.04 35)"},muted:{DEFAULT:"oklch(0.94 0.04 50)",foreground:"oklch(0.5 0.05 35)"},accent:{DEFAULT:"oklch(0.88 0.12 50)",foreground:"oklch(0.21 0.05 35)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.015 50)",foreground:"oklch(0.21 0.04 35)"},popover:{DEFAULT:"oklch(0.99 0.015 50)",foreground:"oklch(0.21 0.04 35)"},border:"oklch(0.89 0.05 50)",input:"oklch(0.89 0.05 50)",ring:"oklch(0.62 0.2 35)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},plum:{name:"plum",colors:{background:"oklch(0.985 0.013 330)",foreground:"oklch(0.22 0.04 330)",primary:{DEFAULT:"oklch(0.45 0.16 330)",foreground:"oklch(0.985 0.01 330)"},secondary:{DEFAULT:"oklch(0.94 0.03 330)",foreground:"oklch(0.22 0.04 330)"},muted:{DEFAULT:"oklch(0.95 0.025 330)",foreground:"oklch(0.5 0.04 330)"},accent:{DEFAULT:"oklch(0.88 0.08 330)",foreground:"oklch(0.22 0.04 330)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 330)",foreground:"oklch(0.22 0.04 330)"},popover:{DEFAULT:"oklch(0.99 0.01 330)",foreground:"oklch(0.22 0.04 330)"},border:"oklch(0.9 0.04 330)",input:"oklch(0.9 0.04 330)",ring:"oklch(0.45 0.16 330)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius}},PRESET_NAMES=Object.keys(PRESETS);async function applyPreset(folder,presetName,themeName="default"){let preset=PRESETS[presetName];if(!preset)return err(unknownPreset(presetName,`Known presets: ${Object.keys(PRESETS).join(", ")}`));return ok(await persistNamedTheme(folder,themeName,{...preset}))}import{join as join23}from"path";async function setCustomCss(folder,css){return await writeText(join23(folder.root,"theme","custom.css"),css),folder.customCss=css,ok({css,bytes:Buffer.byteLength(css,"utf8")})}var toOklch=converter_default("oklch");function r3(n,p=3){if(n===void 0||Number.isNaN(n))return 0;let k=10**p;return Math.round(n*k)/k}function formatOklch(o){let l=r3(o.l),c=r3(o.c),h=r3(o.h),a=o.alpha!==void 0&&o.alpha<1?` / ${r3(o.alpha)}`:"";return`oklch(${l} ${c} ${h}${a})`}function parseOklch2(input){let parsed=parse_default(input);if(!parsed)return err(invalidColor(`Could not parse color: ${JSON.stringify(input)}`,"Accepts #rrggbb, named colors, rgb(), hsl(), oklch(), etc."));let o=toOklch(parsed);return ok({mode:"oklch",l:o.l??0.5,c:o.c??0,h:o.h,alpha:o.alpha})}function parseOklchOrZero(input){let parsed=parse_default(input);if(!parsed)return{mode:"oklch",l:0.5,c:0};let o=toOklch(parsed);return{mode:"oklch",l:o.l??0.5,c:o.c??0,h:o.h,alpha:o.alpha}}function withL(seed,l){return formatOklch({...seed,l})}function lowChroma(seed,l,factor=0.05){return formatOklch({...seed,l,c:seed.c*factor})}function ensureContrast(fg,bg,minRatio=4.5){if(contrast(fg,bg)>=minRatio)return{color:fg,adjusted:!1};let fgOk=parseOklchOrZero(fg),bgOk=parseOklchOrZero(bg),goingDark=fgOk.l<=bgOk.l,current=fgOk;for(let step=0;step<20;step++){current={...current,l:goingDark?Math.max(0,current.l-0.05):Math.min(1,current.l+0.05)};let next=formatOklch(current);if(contrast(next,bg)>=minRatio)return{color:next,adjusted:!0};if(current.l<=0.02||current.l>=0.98)break}return{color:goingDark?"oklch(0 0 0)":"oklch(1 0 0)",adjusted:!0}}function derivePalette(seedColor,current,name){let seedR=parseOklch2(seedColor);if(!seedR.ok)return seedR;let seed=seedR.value,adjustments=[],background="oklch(1 0 0)",foreground="oklch(0.145 0 0)",primaryDefault=withL(seed,0.55),rawPrimaryFg="oklch(0.985 0 0)",primaryFg=ensureContrast(rawPrimaryFg,primaryDefault);if(primaryFg.adjusted)adjustments.push({slot:"primary.foreground",from:rawPrimaryFg,to:primaryFg.color});let secondaryDefault=lowChroma(seed,0.95,0.1),secondaryFg=ensureContrast("oklch(0.2 0 0)",secondaryDefault);if(secondaryFg.adjusted)adjustments.push({slot:"secondary.foreground",from:"oklch(0.2 0 0)",to:secondaryFg.color});let accentDefault=lowChroma(seed,0.93,0.18),accentFg=ensureContrast("oklch(0.2 0 0)",accentDefault);if(accentFg.adjusted)adjustments.push({slot:"accent.foreground",from:"oklch(0.2 0 0)",to:accentFg.color});let colors={background,foreground,primary:{DEFAULT:primaryDefault,foreground:primaryFg.color},secondary:{DEFAULT:secondaryDefault,foreground:secondaryFg.color},muted:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.556 0 0)"},accent:{DEFAULT:accentDefault,foreground:accentFg.color},destructive:{DEFAULT:"oklch(0.577 0.245 27.325)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},popover:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},border:"oklch(0.922 0 0)",input:"oklch(0.922 0 0)",ring:lowChroma(seed,0.708,0.5)},darkFg="oklch(0.985 0 0)",darkPrimary=withL(seed,0.72),darkPrimaryFg=ensureContrast("oklch(0.16 0 0)",darkPrimary);if(darkPrimaryFg.adjusted)adjustments.push({slot:"dark:primary.foreground",from:"oklch(0.16 0 0)",to:darkPrimaryFg.color});let darkAccent=lowChroma(seed,0.3,0.6),darkAccentFg=ensureContrast(darkFg,darkAccent);if(darkAccentFg.adjusted)adjustments.push({slot:"dark:accent.foreground",from:darkFg,to:darkAccentFg.color});let darkDestructive="oklch(0.62 0.21 25)",darkDestructiveFg=ensureContrast(darkFg,darkDestructive),colorsDark={background:lowChroma(seed,0.16,0.3),foreground:darkFg,primary:{DEFAULT:darkPrimary,foreground:darkPrimaryFg.color},secondary:{DEFAULT:lowChroma(seed,0.27,0.35),foreground:darkFg},muted:{DEFAULT:lowChroma(seed,0.27,0.35),foreground:"oklch(0.708 0 0)"},accent:{DEFAULT:darkAccent,foreground:darkAccentFg.color},destructive:{DEFAULT:darkDestructive,foreground:darkDestructiveFg.color},card:{DEFAULT:lowChroma(seed,0.205,0.3),foreground:darkFg},popover:{DEFAULT:lowChroma(seed,0.205,0.3),foreground:darkFg},border:"oklch(1 0 0 / 0.1)",input:"oklch(1 0 0 / 0.15)",ring:darkPrimary};return ok({theme:{...current,name:name??current.name,colors,colorsDark},adjustments})}function pairEntries(prefix,value){if(typeof value==="string")return[[prefix,value]];let out=[[`${prefix}.DEFAULT`,value.DEFAULT]];if(value.foreground!==void 0)out.push([`${prefix}.foreground`,value.foreground]);return out}function tokenAt(theme,path){let cursor=theme;for(let seg of path.split(".")){if(typeof cursor!=="object"||cursor===null)return null;cursor=cursor[seg]}if(typeof cursor==="string")return cursor;if(typeof cursor==="number")return String(cursor);if(typeof cursor==="object"&&cursor!==null&&"DEFAULT"in cursor){let d=cursor.DEFAULT;return typeof d==="string"?d:null}return null}async function importThemeCss(folder,css,opts={}){let parsed=parseThemeCss(css),extend=opts.tailwindConfig?parseThemeExtend(opts.tailwindConfig):null,container=opts.tailwindConfig?parseTailwindContainer(opts.tailwindConfig):null;if(!(Object.keys(parsed.colors).length>0||Object.keys(parsed.colorsDark).length>0||Object.keys(parsed.palette).length>0||Object.keys(parsed.paletteDark).length>0||parsed.radius!==void 0||parsed.fontFamily!==void 0||extend!==null||container!==null))return err(themeBadRequest("no recognizable theme tokens in the CSS \u2014 expected shadcn-convention custom properties (`--background`, `--primary`, \u2026) in `:root`/`.dark`, or Tailwind v4 `--color-*` vars in `@theme`"));let current=themeByName(folder,opts.themeName??"default"),next=JSON.parse(JSON.stringify(current)),warnings=[...parsed.warnings],changes=[],record=(token,to)=>{let from=tokenAt(current,token);if(from!==to)changes.push({token,from,to})},semanticSlots=new Set,paletteNames=new Set;for(let[slot,value]of Object.entries(parsed.colors)){if(value===void 0)continue;semanticSlots.add(slot),next.colors[slot]=value;for(let[token,to]of pairEntries(`colors.${slot}`,value))record(token,to)}if(Object.keys(parsed.colorsDark).length>0){next.colorsDark={...next.colorsDark??{}};for(let[slot,value]of Object.entries(parsed.colorsDark)){if(value===void 0)continue;semanticSlots.add(slot),next.colorsDark[slot]=value;for(let[token,to]of pairEntries(`colorsDark.${slot}`,value))record(token,to)}}if(Object.keys(parsed.palette).length>0){next.palette={...next.palette??{}};for(let[name,value]of Object.entries(parsed.palette))paletteNames.add(name),next.palette[name]=value,record(`palette.${name}`,value)}if(Object.keys(parsed.paletteDark).length>0){next.paletteDark={...next.paletteDark??{}};for(let[name,value]of Object.entries(parsed.paletteDark))paletteNames.add(name),next.paletteDark[name]=value,record(`paletteDark.${name}`,value)}if(parsed.radius!==void 0)next.radius={...next.radius,md:parsed.radius},record("radius.md",parsed.radius);if(parsed.fontFamily!==void 0){next.typography={...next.typography,fontFamily:{...next.typography.fontFamily??{},...parsed.fontFamily}};for(let[role,stack]of Object.entries(parsed.fontFamily))record(`typography.fontFamily.${role}`,stack)}if(extend){if(extend.colors){next.palette={...next.palette??{}};for(let[name,value]of Object.entries(extend.colors)){if(paletteName(name)===null)continue;if(name in next.palette)continue;let resolved=value.includes("var(")?resolveCssVars(value,parsed.rootVars):value;if(resolved===null||resolved===""){warnings.push(`tailwind.config color "${name}" references a CSS var not declared in the imported stylesheet \u2014 skipped`);continue}paletteNames.add(name),next.palette[name]=resolved,record(`palette.${name}`,resolved)}}if(extend.spacing){next.spacing={...next.spacing};for(let[name,value]of Object.entries(extend.spacing)){if(name in next.spacing)continue;next.spacing[name]=value,record(`spacing.${name}`,value)}}if(extend.fontFamily){let fontFamily={...next.typography.fontFamily??{}};for(let[role,stack]of Object.entries(extend.fontFamily)){if(role in fontFamily)continue;fontFamily[role]=stack,record(`typography.fontFamily.${role}`,stack)}next.typography={...next.typography,fontFamily}}if(extend.boxShadow){next.shadows={...next.shadows??{}};for(let[name,value]of Object.entries(extend.boxShadow))next.shadows[name]=value,record(`shadows.${name}`,value)}if(extend.keyframes){next.keyframes={...next.keyframes??{},...extend.keyframes};for(let[name,steps]of Object.entries(extend.keyframes))record(`keyframes.${name}`,Object.keys(steps).join(", "))}if(extend.animation){next.animation={...next.animation??{},...extend.animation};for(let[name,value]of Object.entries(extend.animation))record(`animation.${name}`,value)}}if(container){if(next.container={...next.container??{},...container},container.center!==void 0)record("container.center",String(container.center));if(container.padding!==void 0)record("container.padding",container.padding);if(container.maxWidth!==void 0)record("container.maxWidth",container.maxWidth)}let semanticTotal=Object.keys(ColorsSchema.shape).length,coverage={semantic:semanticSlots.size,semanticTotal,palette:paletteNames.size,unmapped:Object.keys(ColorsSchema.shape).filter((slot)=>!semanticSlots.has(slot))};if(semanticSlots.size===0&&paletteNames.size>0)warnings.push(`none of the ${semanticTotal} semantic color slots matched \u2014 all ${paletteNames.size} imported colors landed in \`palette.*\`, which does NOT theme the canvas. `+"This app names its custom properties on its own convention rather than shadcn's (`--background`, `--primary`, \u2026), so the design will still render on the previous palette. "+'Map the slots yourself with set_theme { tokens: { "colors.background": "<a palette value>", \u2026 } }.');let validated=ThemeSchema.safeParse(next);if(!validated.success){let issue=validated.error.issues[0];return err(invalidThemePath(`imported tokens produced an invalid theme at "${issue?.path.join(".")}": ${issue?.message??"schema mismatch"}`))}if(!opts.apply)return ok({theme:validated.data,changes,coverage,warnings,applied:!1});let persisted=await persistNamedTheme(folder,opts.themeName??"default",validated.data);return ok({theme:persisted,changes,coverage,warnings,applied:!0})}var DEFAULT_FALLBACK={serif:"ui-serif, Georgia, serif",mono:"ui-monospace, SFMono-Regular, monospace"};function familyParam(family){return family.trim().replace(/ +/g,"+")}function leadFamily(stack){return/^\s*(?:"([^"]+)"|'([^']+)'|([^,]+))/.exec(stack)?.slice(1).find(Boolean)?.trim()}var FACE_FIELDS=["fontBody","fontHeading","fontMono"];function typesetsUsing(theme,role){return Object.entries(theme.typography.typesets??{}).filter(([,typeset])=>FACE_FIELDS.some((field)=>typeset[field]===role)).map(([name])=>name)}function pruneGoogleFonts(entries,fontFamily){let declared=new Set(Object.values(fontFamily).map(leadFamily).filter((f)=>f!==void 0).map(familyParam)),seen=new Set;return entries.filter((entry)=>{let family=familyParam(entry.split(":")[0]??"");if(!declared.has(family)||seen.has(family))return!1;return seen.add(family),!0})}async function setFonts(folder,fonts,themeName="default"){let base=themeByName(folder,themeName),fontFamily={...base.typography.fontFamily??{}},googleFonts=[...base.typography.googleFonts??[]];for(let spec of fonts){if(!/^[a-z][a-z0-9-]*$/.test(spec.role))return err(invalidThemePath(`font role "${spec.role}" must be utility-name safe (lowercase letters, digits, dashes)`));if(spec.remove){if(!(spec.role in fontFamily))return err(invalidThemePath(`font role "${spec.role}" does not exist, so it cannot be removed`));let used=typesetsUsing(base,spec.role);if(used.length>0)return err(invalidThemePath(`font role "${spec.role}" is still used by the ${used.join(", ")} typeset${used.length>1?"s":""} \u2014 point ${used.length>1?"those faces":"that face"} elsewhere first`));delete fontFamily[spec.role];continue}if(!spec.family)return err(invalidThemePath(`font role "${spec.role}": family is required`));let fallback=spec.fallback??DEFAULT_FALLBACK[spec.role]??"ui-sans-serif, system-ui, sans-serif";if(fontFamily[spec.role]=`"${spec.family}", ${fallback}`,spec.google){let param=familyParam(spec.family),entry=spec.google===!0?param:`${param}:${spec.google}`,existing=googleFonts.findIndex((g)=>familyParam(g.split(":")[0]??"")===param);if(existing===-1)googleFonts.push(entry);else googleFonts[existing]=entry}}let next={...base,typography:{...base.typography,fontFamily,googleFonts:pruneGoogleFonts(googleFonts,fontFamily)}},parsed=ThemeSchema.safeParse(next);if(!parsed.success)return err(invalidThemePath(parsed.error.issues[0]?.message??"invalid theme"));return{ok:!0,value:await persistNamedTheme(folder,themeName,parsed.data)}}function applyPath(target,path,value){if(!path)return"path is required";let segments=path.split(".");if(segments.some((s)=>s===""||s==="__proto__"||s==="constructor"||s==="prototype"))return`bad path: ${JSON.stringify(path)}`;let cursor=target;for(let i=0;i<segments.length-1;i++){let k=segments[i];if(k==="__proto__"||k==="constructor"||k==="prototype")return`bad path: ${JSON.stringify(path)}`;let existing=cursor[k];if(existing===void 0||typeof existing!=="object"||existing===null)cursor[k]={};cursor=cursor[k]}return cursor[segments[segments.length-1]]=value,null}function valueAt(source,path){let cursor=source;for(let segment of path.split(".")){if(cursor===null||typeof cursor!=="object")return;cursor=cursor[segment]}return cursor}async function setTokens(folder,entries,themeName="default"){let working=JSON.parse(JSON.stringify(themeByName(folder,themeName))),applied=[],failed=[];for(let{path,value}of entries){let candidate=JSON.parse(JSON.stringify(working)),pathError=applyPath(candidate,path,value);if(pathError!==null){failed.push({path,reason:pathError});continue}let parsed=ThemeSchema.safeParse(candidate);if(!parsed.success){let issue=parsed.error.issues[0],where=issue?.path.join(".")||path;failed.push({path,reason:`Setting ${path} to ${JSON.stringify(value)} produced an invalid theme at "${where}": ${issue?.message??"schema mismatch"}`});continue}if(valueAt(parsed.data,path)===void 0){failed.push({path,reason:`"${path}" is not a token this theme defines, so the write was dropped. Check the slot name (e.g. "colors.primary.DEFAULT", "colorsDark.background"), or use "palette.<name>" for a raw brand value.`});continue}working=parsed.data,applied.push(path)}if(failed.length>0)return err({kind:"BulkTokensInvalid",applied,failed});let finalTheme=working,coalesceKey=`token:${applied.join(",")}`,persisted=await tryCatchAsync(()=>persistNamedTheme(folder,themeName,finalTheme,coalesceKey),(e)=>invalidThemePath(`Persisting theme "${themeName}" failed: ${e.message}`));return persisted.ok?ok({theme:persisted.value,applied}):persisted}async function setToken(folder,path,value,themeName="default"){let r=await setTokens(folder,[{path,value}],themeName);if(r.ok)return ok(r.value.theme);if(r.error.kind==="BulkTokensInvalid")return err(invalidThemePath(r.error.failed[0]?.reason??"invalid token"));return r}var FACE_FIELDS2=["fontBody","fontHeading","fontMono"];function merge2(current,spec){let next={...current};if(spec.size!==void 0)if(spec.size===null)delete next.size;else next.size=spec.size;if(spec.leading!==void 0)if(spec.leading===null)delete next.leading;else next.leading=spec.leading;if(spec.flow!==void 0)if(spec.flow===null)delete next.flow;else next.flow=spec.flow;for(let field of FACE_FIELDS2){let value=spec[field];if(value===void 0)continue;if(value===null)delete next[field];else next[field]=value}return next}async function setTypeset(folder,typesets,themeName="default"){let base=themeByName(folder,themeName),declaredFaces=base.typography.fontFamily??{},next={...base.typography.typesets??{}};for(let spec of typesets){let name=spec.name??DEFAULT_TYPESET_NAME;if(!isTypesetName(name))return err(invalidThemePath(`typeset name "${name}" must be selector-safe (letters, digits, dash, underscore) \u2014 it is emitted as a \`.typeset-${name}\` class`));if(spec.remove&&spec.renameTo!==void 0)return err(invalidThemePath(`typeset "${name}": pass either remove or renameTo, not both`));if(spec.remove||spec.renameTo!==void 0){let verb=spec.remove?"removed":"renamed";if(name===DEFAULT_TYPESET_NAME)return err(invalidThemePath(`the "${DEFAULT_TYPESET_NAME}" typeset is the baseline every preset inherits from \u2014 it cannot be ${verb}`));if(!(name in next))return err(invalidThemePath(`typeset "${name}" does not exist, so it cannot be ${verb}`))}if(spec.remove){delete next[name];continue}let target=name;if(spec.renameTo!==void 0&&spec.renameTo!==name){if(!isTypesetName(spec.renameTo))return err(invalidThemePath(`typeset name "${spec.renameTo}" must be selector-safe (letters, digits, dash, underscore) \u2014 it is emitted as a \`.typeset-${spec.renameTo}\` class`));if(spec.renameTo in next)return err(invalidThemePath(`typeset "${spec.renameTo}" already exists`));next[spec.renameTo]=next[name]??{},delete next[name],target=spec.renameTo}if(spec.leading!==void 0&&spec.leading!==null&&spec.leading<=0)return err(invalidThemePath(`typeset "${name}": leading must be a positive number`));for(let field of FACE_FIELDS2){let role=spec[field];if(role===void 0||role===null)continue;if(!(role in declaredFaces)){let known=Object.keys(declaredFaces).sort().join(", ")||"none declared";return err(invalidThemePath(`typeset "${name}": ${field} "${role}" is not a declared font role (have: ${known}). Declare it with set_fonts first.`))}if(TYPESET_SCALE_NAMES.includes(role))return err(invalidThemePath(`typeset "${name}": font role "${role}" collides with the type scale \u2014 rename the role (it would make \`font-${role}\` ambiguous)`))}next[target]=merge2(next[target]??{},spec)}let candidate={...base,typography:{...base.typography,typesets:next}},parsed=ThemeSchema.safeParse(candidate);if(!parsed.success)return err(invalidThemePath(parsed.error.issues[0]?.message??"invalid theme"));return{ok:!0,value:await persistNamedTheme(folder,themeName,parsed.data,coalesceKey(typesets))}}function coalesceKey(typesets){let parts=[];for(let spec of typesets){if(spec.remove||spec.renameTo!==void 0)return;let fields=Object.keys(spec).filter((k)=>k!=="name"&&spec[k]!==void 0).sort();if(fields.length===0)return;parts.push(`${spec.name??DEFAULT_TYPESET_NAME}:${fields.join(",")}`)}return parts.length>0?`typeset:${parts.join("|")}`:void 0}function srgbChannel(c){return c<=0.03928?c/12.92:((c+0.055)/1.055)**2.4}function relativeLuminance(rgb){let r=srgbChannel(rgb.r),g=srgbChannel(rgb.g),b=srgbChannel(rgb.b);return 0.2126*r+0.7152*g+0.0722*b}function parseToRgb(color){let parsed=parse_default(color);if(!parsed)return null;let hex=formatHex(parsed);if(!hex)return null;let stripped=hex.replace(/^#/,"");if(stripped.length!==6)return null;let r=Number.parseInt(stripped.slice(0,2),16)/255,g=Number.parseInt(stripped.slice(2,4),16)/255,b=Number.parseInt(stripped.slice(4,6),16)/255;return{r,g,b}}function contrastRatio(fg,bg){let fgRgb=parseToRgb(fg),bgRgb=parseToRgb(bg);if(!fgRgb||!bgRgb)return null;let l1=relativeLuminance(fgRgb),l2=relativeLuminance(bgRgb),lighter=Math.max(l1,l2),darker=Math.min(l1,l2);return(lighter+0.05)/(darker+0.05)}function tierForRatio(ratio){if(ratio>=7)return"AAA";if(ratio>=4.5)return"AA";if(ratio>=3)return"AAlarge";return"Fail"}function colorValue2(slot){if(typeof slot==="string")return slot;if(slot&&typeof slot==="object"&&"DEFAULT"in slot){let def=slot.DEFAULT;return typeof def==="string"?def:void 0}return}function fgValue(slot){if(slot&&typeof slot==="object"&&"foreground"in slot){let fg=slot.foreground;return typeof fg==="string"?fg:void 0}return}function scoreThemeContrast(theme,mode="light"){let c=resolveColors(theme,mode==="dark"),pairs=[{label:"foreground on background",fg:c.foreground,bg:c.background},{label:"primary-foreground on primary",fg:fgValue(c.primary),bg:colorValue2(c.primary)},{label:"secondary-foreground on secondary",fg:fgValue(c.secondary),bg:colorValue2(c.secondary)},{label:"accent-foreground on accent",fg:fgValue(c.accent),bg:colorValue2(c.accent)},{label:"muted-foreground on muted",fg:fgValue(c.muted),bg:colorValue2(c.muted)},{label:"card-foreground on card",fg:fgValue(c.card),bg:colorValue2(c.card)},{label:"destructive-foreground on destructive",fg:fgValue(c.destructive),bg:colorValue2(c.destructive)},{label:"foreground on card",fg:c.foreground,bg:colorValue2(c.card)},{label:"muted-foreground on background",fg:fgValue(c.muted),bg:c.background}],out=[];for(let p of pairs){if(!p.fg||!p.bg)continue;let ratio=contrastRatio(p.fg,p.bg);if(ratio===null)continue;out.push({label:p.label,mode,fg:p.fg,bg:p.bg,ratio:Math.round(ratio*100)/100,tier:tierForRatio(ratio)})}return out}function scoreThemeContrastBoth(theme){return[...scoreThemeContrast(theme,"light"),...scoreThemeContrast(theme,"dark")]}var themeLocks=createLockMap();function withThemeLock(folder,fn){return themeLocks.run(folder.root,fn)}function broadcastThemeChanged(ctx){ctx.broadcast({type:"theme-changed"})}async function setToken2(ctx,path,value,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setToken(ctx.folder,path,value,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_token",{token:path,...themeName?{themeName}:{}});return r})}async function setTokens2(ctx,entries,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setTokens(ctx.folder,entries,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_tokens",{...entries[0]?{token:entries[0].path}:{},...themeName?{themeName}:{}});return r})}async function setFonts2(ctx,fonts,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setFonts(ctx.folder,fonts,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_fonts",themeName?{themeName}:{});return r})}async function setTypeset2(ctx,typesets,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setTypeset(ctx.folder,typesets,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_typeset",themeName?{themeName}:{});return r})}async function addTheme(ctx,name,from,overwrite=!1){return withThemeLock(ctx.folder,async()=>{if(!/^[a-z][a-z0-9-]*$/.test(name)||name==="default")return{ok:!1,error:{kind:"InvalidThemePath",reason:`theme name "${name}" must be lowercase kebab (and not "default")`}};if(!overwrite&&ctx.folder.themes.has(name))return{ok:!1,error:{kind:"InvalidThemePath",reason:`theme "${name}" already exists (pass overwrite: true to replace)`}};let source=themeByName(ctx.folder,from),cloned=JSON.parse(JSON.stringify({...source,name})),persisted=await persistNamedTheme(ctx.folder,name,cloned);return broadcastThemeChanged(ctx),emitActivity(ctx,"add_theme",{themeName:name}),{ok:!0,value:{name,theme:persisted}}})}async function renameTheme(ctx,name,renameTo){return withThemeLock(ctx.folder,async()=>{let guard=namedThemeGuard(ctx,name,"rename");if(guard)return guard;if(!/^[a-z][a-z0-9-]*$/.test(renameTo)||renameTo==="default")return themeErr(`theme name "${renameTo}" must be lowercase kebab (and not "default")`);if(ctx.folder.themes.has(renameTo))return themeErr(`theme "${renameTo}" already exists`);let source=ctx.folder.themes.get(name);await persistNamedTheme(ctx.folder,renameTo,{...source,name:renameTo}),await rm3(join24(ctx.folder.root,"theme",`${name}.json`),{force:!0}),ctx.folder.themes.delete(name);let repointedBoards=[];for(let board of ctx.folder.boards.values()){if(board.theme!==name)continue;await persistBoard(ctx.folder,board.id,{...board,theme:renameTo}),repointedBoards.push(board.id)}return broadcastThemeChanged(ctx),emitActivity(ctx,"rename_theme",{themeName:renameTo}),{ok:!0,value:{name:renameTo,repointedBoards}}})}async function removeTheme(ctx,name){return withThemeLock(ctx.folder,async()=>{let guard=namedThemeGuard(ctx,name,"remove");if(guard)return guard;let pinned=[...ctx.folder.boards.values()].filter((b)=>b.theme===name).map((b)=>b.id);if(pinned.length>0)return themeErr(`theme "${name}" is pinned by ${pinned.join(", ")} \u2014 repoint or unpin those boards first (update_board { patch: { theme: null } })`);return await rm3(join24(ctx.folder.root,"theme",`${name}.json`),{force:!0}),ctx.folder.themes.delete(name),broadcastThemeChanged(ctx),emitActivity(ctx,"remove_theme",{themeName:name}),{ok:!0,value:{removedTheme:name}}})}var themeErr=(reason)=>({ok:!1,error:{kind:"InvalidThemePath",reason}});function namedThemeGuard(ctx,name,verb){if(name==="default")return themeErr(`cannot ${verb} the default theme`);if(!ctx.folder.themes.has(name))return themeErr(`theme "${name}" does not exist`);return null}function listThemes(ctx){return{themes:[...ctx.folder.themes.keys()].sort().map((name)=>({name,usedByBoards:[...ctx.folder.boards.values()].filter((b)=>(b.theme??"default")===name).map((b)=>b.id)}))}}async function setCustomCss2(ctx,css){return withThemeLock(ctx.folder,async()=>{let r=await setCustomCss(ctx.folder,css);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"custom_css",{});return r})}async function applyPreset2(ctx,presetName,themeName){return withThemeLock(ctx.folder,async()=>{let r=await applyPreset(ctx.folder,presetName,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"apply_preset",themeName?{themeName}:{});return r})}async function derivePaletteFromColor(ctx,seedColor,name){return withThemeLock(ctx.folder,()=>DoAsync(async function*(){let result=yield*$(derivePalette(seedColor,ctx.folder.theme,name)),persisted=await persistNamedTheme(ctx.folder,name??"default",result.theme);return broadcastThemeChanged(ctx),emitActivity(ctx,"derive_palette_from_color",name?{themeName:name}:{}),{...result,theme:persisted}}))}async function importThemeCss2(ctx,css,opts={}){return withThemeLock(ctx.folder,async()=>{let r=await importThemeCss(ctx.folder,css,opts);if(r.ok&&r.value.applied)broadcastThemeChanged(ctx),emitActivity(ctx,"import_theme",opts.themeName?{themeName:opts.themeName}:{});return r})}var SetTokenBody=object({path:string().min(1),value:union([string(),number()]),theme:string().min(1).optional()}),ApplyPresetBody=object({presetName:string().min(1),theme:string().min(1).optional()}),DeriveFromColorBody=object({seedColor:string().min(1),name:string().min(1).optional()}),AddThemeBody=object({name:string().min(1),from:string().min(1).optional()}),TypesetSpecBody=object({name:string().min(1).optional(),renameTo:string().min(1).optional(),remove:boolean().optional(),size:union([string(),number(),_null()]).optional(),leading:union([number(),_null()]).optional(),flow:union([string(),number(),_null()]).optional(),fontBody:union([string(),_null()]).optional(),fontHeading:union([string(),_null()]).optional(),fontMono:union([string(),_null()]).optional()}),SetTypesetBody=object({typesets:array(TypesetSpecBody).min(1),theme:string().min(1).optional()}),FontSpecBody=object({role:string().min(1),family:string().min(1).optional(),fallback:string().min(1).optional(),google:union([string().min(1),literal(!0)]).optional(),remove:boolean().optional()}),SetFontsBody=object({fonts:array(FontSpecBody).min(1),theme:string().min(1).optional()});function createThemeRouter(ctxFor){let r=new Hono2,route=makeThemeRoute(ctxFor);return r.get("/",(c)=>c.json(themeByName(ctxFor().folder,c.req.query("name")))),r.get("/presets",(c)=>c.json({presets:PRESET_NAMES})),r.get("/preset-summaries",(c)=>{let summaries=PRESET_NAMES.map((name)=>{let t=PRESETS[name];if(!t)return null;return{name,swatches:{background:t.colors.background,primary:t.colors.primary?.DEFAULT??t.colors.primary,accent:t.colors.accent?.DEFAULT??t.colors.accent,foreground:t.colors.foreground}}}).filter((x)=>x!==null);return c.json({presets:summaries})}),r.get("/contrast",(c)=>c.json({results:scoreThemeContrast(themeByName(ctxFor().folder,c.req.query("name")))})),r.post("/set_token",route(SetTokenBody,async(args,ctx)=>map(await setToken2(ctx,args.path,args.value,args.theme),(theme)=>({theme})))),r.post("/apply_preset",route(ApplyPresetBody,async(args,ctx)=>map(await applyPreset2(ctx,args.presetName,args.theme),(theme)=>({theme})))),r.post("/set_typeset",route(SetTypesetBody,async(args,ctx)=>map(await setTypeset2(ctx,args.typesets,args.theme),(theme)=>({theme})))),r.post("/set_fonts",route(SetFontsBody,async(args,ctx)=>map(await setFonts2(ctx,args.fonts,args.theme),(theme)=>({theme})))),r.post("/add_theme",route(AddThemeBody,(args,ctx)=>addTheme(ctx,args.name,args.from))),r.post("/derive_palette_from_color",route(DeriveFromColorBody,(args,ctx)=>derivePaletteFromColor(ctx,args.seedColor,args.name))),r}import{rm as rm4}from"fs/promises";import{join as join25}from"path";function createUndoRouter(folderFor,broadcast){let r=new Hono2;return r.get("/",(c)=>c.json(folderFor().history.depths())),r.post("/",async(c)=>{let folder=folderFor(),entry=folder.history.popUndo();if(!entry)return c.json({reverted:null,...folder.history.depths()});return c.json({reverted:await applyRevert(entry,folder,broadcast,"redo"),...folder.history.depths()})}),r.post("/redo",async(c)=>{let folder=folderFor(),entry=folder.history.popRedo();if(!entry)return c.json({reverted:null,...folder.history.depths()});return c.json({reverted:await applyRevert(entry,folder,broadcast,"undo"),...folder.history.depths()})}),r}async function applyRevert(entry,folder,broadcast,pushOpposite){if(entry.kind==="screen"){let current=folder.screens.get(entry.screenId)??null,back={kind:"screen",screenId:entry.screenId,screen:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withScreenLock(folder,entry.screenId,async()=>{if(entry.screen===null)await deleteScreen(folder,entry.screenId);else await writeScreen(folder,entry.screenId,entry.screen)}),broadcast({type:"screen-changed",screenId:entry.screenId}),{kind:"screen",screenId:entry.screenId}}if(entry.kind==="board"){let current=folder.boards.get(entry.boardId)??null,back={kind:"board",boardId:entry.boardId,board:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withBoardLock(folder,entry.boardId,async()=>{if(entry.board===null)await deleteBoard(folder,entry.boardId);else await writeBoard(folder,entry.boardId,entry.board)}),broadcast({type:"board-changed",boardId:entry.boardId}),{kind:"board",boardId:entry.boardId}}if(entry.kind==="boards"){let boardIds=entry.boards.map((b)=>b.boardId),back={kind:"boards",boards:boardIds.map((boardId)=>({boardId,board:folder.boards.get(boardId)??null}))};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);await withBoardLocks(folder,boardIds,async()=>{for(let{boardId,board}of[...entry.boards].reverse())if(board===null)await deleteBoard(folder,boardId);else await writeBoard(folder,boardId,board)});for(let boardId of boardIds)broadcast({type:"board-changed",boardId});return{kind:"boards",boardIds}}if(entry.kind==="snippet"){let current=folder.snippets.get(entry.snippetId)??null,back={kind:"snippet",snippetId:entry.snippetId,snippet:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withSnippetLock(folder,entry.snippetId,async()=>{if(entry.snippet===null)await deleteSnippet(folder,entry.snippetId);else await writeSnippet(folder,entry.snippetId,entry.snippet)}),broadcast({type:"snippet-changed",snippetId:entry.snippetId}),{kind:"snippet",snippetId:entry.snippetId}}let name=entry.themeName,current=name==="default"?folder.theme:folder.themes.get(name)??null,back={kind:"theme",themeName:name,theme:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await writeTheme(folder,name,entry.theme),broadcast({type:"theme-changed"}),{kind:"theme",themeName:name}}async function writeScreen(folder,screenId,screen){await writeJsonAtomic(join25(folder.root,"screens",`${screenId}.json`),screen),folder.screens.set(screenId,screen)}async function deleteScreen(folder,screenId){await rm4(join25(folder.root,"screens",`${screenId}.json`),{force:!0}),folder.screens.delete(screenId)}async function writeBoard(folder,boardId,board){await writeJsonAtomic(join25(folder.root,"boards",`${boardId}.json`),board),folder.boards.set(boardId,board)}async function deleteBoard(folder,boardId){await rm4(join25(folder.root,"boards",`${boardId}.json`),{force:!0}),folder.boards.delete(boardId)}async function writeTheme(folder,name,theme){let path=join25(folder.root,"theme",`${name}.json`);if(theme===null&&name!=="default"){await rm4(path,{force:!0}),folder.themes.delete(name);return}if(theme===null)return;if(await writeJsonAtomic(path,theme),folder.themes.set(name,theme),name==="default")folder.theme=theme}async function writeSnippet(folder,snippetId,snippet){await writeJsonAtomic(join25(folder.root,"snippets",`${snippetId}.json`),snippet),folder.snippets.set(snippetId,snippet)}async function deleteSnippet(folder,snippetId){await rm4(join25(folder.root,"snippets",`${snippetId}.json`),{force:!0}),folder.snippets.delete(snippetId)}var UPDATES_UNAVAILABLE={current:"unknown",latest:null,available:!1,method:"unknown",channel:"unknown",upgradable:!1,reason:"This canvas was not started by the velloo CLI, so it cannot update itself."};function createUpdatesRouter(updates){let app=new Hono2,inFlight=null;return app.get("/status",async(c)=>{if(!updates)return c.json(UPDATES_UNAVAILABLE);let refresh=c.req.query("refresh")==="1";return c.json(await updates.status({refresh}))}),app.post("/",async(c)=>{if(!updates)return c.json({error:{kind:"unavailable",message:UPDATES_UNAVAILABLE.reason}},503);if(inFlight)return c.json({error:{kind:"busy",message:"An upgrade is already running."}},409);let run=updates.upgrade();inFlight=run;try{return c.json(await run)}catch(error){return c.json({error:{kind:"upgrade_failed",message:error instanceof Error?error.message:String(error)}},500)}finally{inFlight=null}}),app}var LOOPBACK_HOSTS=new Set(["localhost","127.0.0.1","[::1]","::1"]);function hostHeaderHostname(host){if(!host)return null;if(host.startsWith("[")){let end=host.indexOf("]");return end===-1?host:host.slice(0,end+1)}let colon=host.lastIndexOf(":");return colon===-1?host:host.slice(0,colon)}function originHostname(origin){if(origin==null)return null;try{return new URL(origin).hostname}catch{return null}}function hostIsLoopback(host){let hostname=hostHeaderHostname(host);return hostname!==null&&LOOPBACK_HOSTS.has(hostname)}function requestIsLocal(headers){if(!hostIsLoopback(headers.host))return!1;if(headers.origin!=null){let origin=originHostname(headers.origin);if(!origin||!LOOPBACK_HOSTS.has(origin))return!1}return!0}function localOnlyMiddleware(){return async(c,next)=>{if(!requestIsLocal({host:new URL(c.req.url).host,origin:c.req.header("origin")}))return c.json({error:"forbidden: request is not from a local origin"},403);return next()}}function createApp(ctxFor,jit,bundler,canvasBundler,auth,publish,cloud,comments,updates){let app=new Hono2,folder=()=>ctxFor().folder;return app.use("*",localOnlyMiddleware()),app.get("/api/health",(c)=>c.json({ok:!0,app:"velloo",root:ctxFor().folder.root,pid:process.pid})),app.get("/api/activity",(c)=>c.json({events:activityLog(ctxFor().folder.root)})),app.route("/api/design",createDesignRouter(ctxFor)),app.route("/api/config",createConfigRouter(ctxFor)),app.route("/api/screen",createScreenRouter(folder)),app.route("/api/board",createBoardRouter(folder)),app.route("/api/snippets",createSnippetsRouter(folder)),app.route("/api/search",createSearchRouter(folder)),app.route("/api/captures",createCapturesRouter(folder)),app.route("/api/comments",createCommentsRouter(comments??new LocalCommentsService(ctxFor))),app.route("/api/assets",createAssetsRouter(folder,cloud)),app.route("/api/render",createRenderRouter(ctxFor,jit,bundler,canvasBundler)),app.route("/api/export",createExportRouter(ctxFor,jit,bundler,canvasBundler)),app.route("/api/preflight",createPreflightRouter(ctxFor)),app.route("/api/live",createLiveRouter(bundler)),app.route("/api/canvas",createCanvasRouter(canvasBundler,()=>ctxFor().folder.config.defaultLibrary)),app.route("/api/components",createComponentsRouter(ctxFor)),app.route("/api/mutate",createMutateRouter(ctxFor)),app.route("/api/theme",createThemeRouter(ctxFor)),app.route("/api/annotations",createAnnotationsRouter(ctxFor)),app.route("/api/notes",createNotesRouter(ctxFor)),app.route("/api/auth",createAuthRouter(auth)),app.route("/api/publish",createPublishRouter(publish)),app.route("/api/updates",createUpdatesRouter(updates)),app.route("/api/undo",createUndoRouter(folder,(e)=>ctxFor().broadcast(e))),app}class Broadcaster{clients=new Set;register(ws){this.clients.add(ws)}unregister(ws){this.clients.delete(ws)}broadcast(event){let payload=JSON.stringify(event);for(let ws of this.clients)try{ws.send(payload)}catch{}}size(){return this.clients.size}}import{mkdir as mkdir9,writeFile as writeFile8}from"fs/promises";import{tmpdir as tmpdir2}from"os";import{dirname as dirname15,join as join27}from"path";import{existsSync as existsSync7}from"fs";import{dirname as dirname14,join as join26}from"path";import{fileURLToPath as fileURLToPath2}from"url";var here2=dirname14(fileURLToPath2(import.meta.url));function resolveDir2(){return[process.env.VELLOO_SCHEMA_SRC,join26(here2,"pkgs","schema","src"),here2].filter((p)=>Boolean(p)).find((d)=>existsSync7(join26(d,"typeset.ts")))??here2}var schemaSrcDir=resolveDir2();var EMPTY=`export function mountScreen() {}
|
|
220
|
-
`;async function buildCanvasBundle(hostRoot,spec,componentIds,aliases=[],minify=!1){let errors=[],runtimePaths=resolveRuntime(hostRoot,spec.styleRuntime,errors);if(!runtimePaths)return{code:EMPTY,errors,usable:!1,diagnostics:[]};let overlayIds=new Set(spec.overlayIds??[]),requested=new Set(componentIds);if([...overlayIds].some((id)=>requested.has(id)))requested.add("Paper"),requested.add("Box");let declared;try{declared=await spec.components([...requested])}catch(error){return errors.push({message:messageOf(error)}),{code:EMPTY,errors,usable:!1,diagnostics:[]}}let byId=new Map(declared.map((entry)=>[entry.id,entry])),diagnostics=[],resolved=[],preflight=new Map,plugins=[aliasPlugin(hostRoot,aliases),hostRuntimePlugin(hostRoot),vellooSourcePlugin(),...radixShimPlugin(hostRoot)??[]];for(let id of requested){if(overlayIds.has(id)){let note=byId.get(id)?.sources[0]?.note;diagnostics.push({id,status:"adapted",...note?{note}:{}});continue}let component=byId.get(id);if(!component||component.sources.length===0){diagnostics.push({id,status:"unavailable",note:"No browser-canvas source is registered for this ref, so the screen keeps its server render instead of client-mounting."});continue}let failures=[],chosen;for(let source of component.sources){let path;try{path=resolveImport(source.importPath,hostRoot,aliases)}catch(error){failures.push(`${source.importPath}: ${messageOf(error)}`);continue}if(source.preflight){let check=preflight.get(path);if(!check)check=preflightSource(path,plugins),preflight.set(path,check);let compileErrors=await check;if(compileErrors.length>0){failures.push(...compileErrors.map((error)=>`${source.importPath}: ${error}`));continue}}chosen={source,path};break}if(!chosen){let note=component.sources.at(-1)?.note;diagnostics.push({id,status:"unavailable",...note?{note}:{},...failures.length>0?{errors:failures}:{}});continue}resolved.push({id,path:chosen.path,exportName:chosen.source.exportName??id}),diagnostics.push({id,status:chosen.source.fidelity,importPath:chosen.source.importPath,...chosen.source.note?{note:chosen.source.note}:{},...failures.length>0?{errors:failures}:{}})}let unavailable=diagnostics.filter((entry)=>entry.status==="unavailable");if(unavailable.length>0||resolved.length===0){for(let entry of unavailable)errors.push({message:`${entry.id}: ${entry.errors?.join("; ")??"no browser source"}`});return{code:EMPTY,errors,usable:!1,diagnostics}}let entrySource=buildCanvasEntry({...runtimePaths,styleRuntime:spec.styleRuntime,components:resolved,overlayIds:spec.overlayIds??[],diagnostics}),key=Bun.hash(`${hostRoot}:${JSON.stringify(resolved.map((entry)=>[entry.id,entry.path,entry.exportName]))}:${JSON.stringify(spec.styleRuntime)}`).toString(16);try{let dir=
|
|
219
|
+
`),hint:"Most of these are parts placed without their parent. Fix a few and the rest of the screen draws again."}:{title:"This screen didn't render",lede:"A component threw an error the canvas could not pin on a single node, so it could not stand in for it and draw the rest. Every other screen is unaffected.",detail:err instanceof Error?err.message:String(err),hint:"The message above is the component's own. It usually names the prop or the parent it needs."};return c.body(renderErrorDocument({...page,screenName:screen.name,dark}),err instanceof UnknownComponentError?422:500,headers)}};return r.get("/snippet/:snippetId",async(c)=>{let ctx=ctxFor(),snippet=ctx.folder.snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);let viewport=parseViewport(c,480,320),args={};for(let p of snippet.params){if(p.default!==void 0)continue;args[p.name]=snippetParamPlaceholder(p)}let snippetInstance={$snippet:snippet.id,...Object.keys(args).length>0?{args}:{}},screen={id:`${snippet.id}__preview`,name:`${snippet.name} preview`,tree:{$ref:"Card",props:{className:"min-h-screen w-full flex items-center justify-center p-6 ring-0 shadow-none bg-transparent rounded-none","data-velloo-snippet-preview":"true"},children:[snippetInstance]}};return renderPreview(c,{ctx,screen,viewport,libraryOf:snippet})}),r.get("/snippet-body/:snippetId",async(c)=>{let ctx=ctxFor(),snippet=ctx.folder.snippets.get(c.req.param("snippetId"));if(!snippet)return c.json({error:"snippet not found"},404);let viewport=parseViewport(c,480,320),paramDefaults={};for(let p of snippet.params)paramDefaults[p.name]=snippetParamPlaceholder(p);let tree=resolveSnippetBodyForEdit(snippet.tree,paramDefaults,snippet.id),screen={id:`${snippet.id}__body`,name:`${snippet.name} body`,tree};return renderPreview(c,{ctx,screen,viewport,libraryOf:snippet,selectionRing:!0})}),r.get("/component/:componentId",async(c)=>{let ctx=ctxFor(),componentId=c.req.param("componentId"),viewport=parseViewport(c,480,200),rawProps=c.req.query("props"),propOverrides;if(rawProps)try{let parsed=JSON.parse(rawProps);if(parsed&&typeof parsed==="object"&&!Array.isArray(parsed))propOverrides=parsed}catch{return c.json({error:"invalid props JSON"},400)}let inner=buildShowcaseTree(componentId,propOverrides),tree={$ref:"Card",props:{className:"min-h-screen w-full flex items-center justify-center p-4 ring-0 shadow-none bg-transparent rounded-none"},children:[inner]},screen={id:`${componentId}__preview`,name:`${componentId} preview`,tree};return renderPreview(c,{ctx,screen,viewport})}),r.get("/:screenId",async(c)=>{let ctx=ctxFor(),f=ctx.folder,screen=f.screens.get(c.req.param("screenId"));if(!screen)return c.json({error:"screen not found"},404);let preset=f.config.viewportPresets[0]??{name:"default",w:1440,h:900},viewport=parseViewport(c,preset.w,preset.h);return renderPreview(c,{ctx,screen,viewport,withBundles:!0})}),r}var TEXT_PROPS=["children","label","placeholder","title","alt"],EXCERPT_WINDOW=120;function emptySearchResult(query){return{query,boards:[],screens:[],text:[],textTotal:0}}function nameRank(name,id,q,archived=!1){let prefix=name.toLowerCase().startsWith(q)||id.toLowerCase().startsWith(q)?0:1;return(archived?2:0)+prefix}function nameMatches(name,id,q){return name.toLowerCase().includes(q)||id.toLowerCase().includes(q)}function excerptAround(value,index,queryLength){if(value.length<=EXCERPT_WINDOW)return{excerpt:value,matchStart:index,matchEnd:index+queryLength};let start=Math.max(0,index-40),end=Math.min(value.length,start+EXCERPT_WINDOW),prefix=start>0?"\u2026":"",suffix=end<value.length?"\u2026":"",excerpt=prefix+value.slice(start,end)+suffix,matchStart=prefix.length+(index-start);return{excerpt,matchStart,matchEnd:Math.min(excerpt.length,matchStart+queryLength)}}function searchFolder(folder,rawQuery,textLimit=20){let query=rawQuery.trim(),q=query.toLowerCase();if(q==="")return emptySearchResult(query);let boardsInOrder=orderedBoards(folder),boards=boardsInOrder.filter(([id,b])=>nameMatches(b.name,id,q)).map(([id,b])=>({id,name:b.name,frameCount:b.frames.length,archived:isArchived(b)})).sort((a,b)=>nameRank(a.name,a.id,q,a.archived)-nameRank(b.name,b.id,q,b.archived)),hostBoards=new Map;for(let[boardId,board]of boardsInOrder)for(let frame of board.frames){let hosts=hostBoards.get(frame.screen)??[];if(!hosts.some((h)=>h.id===boardId))hosts.push({id:boardId,name:board.name,archived:isArchived(board)});hostBoards.set(frame.screen,hosts)}for(let hosts of hostBoards.values())hosts.sort((a,b)=>Number(a.archived)-Number(b.archived));let screens=[...folder.screens.entries()].filter(([id,s])=>nameMatches(s.name,id,q)).map(([id,s])=>({id,name:s.name,boards:hostBoards.get(id)??[]})).sort((a,b)=>nameRank(a.name,a.id,q)-nameRank(b.name,b.id,q)),text=[],textTotal=0;function consider(screenId,screenName,node,path,prop,value){let index=value.toLowerCase().indexOf(q);if(index===-1)return!1;if(textTotal+=1,text.length<textLimit){let isSnippet=isSnippetInstance(node);text.push({screenId,screenName,board:hostBoards.get(screenId)?.[0]??null,path,kind:isSnippet?"snippet":"component",ref:isSnippet?node.$snippet:isComponentNode(node)?node.$ref:"",...nodeId(node)?{nodeId:nodeId(node)}:{},prop,...excerptAround(value,index,q.length)})}return!0}function walk(screenId,screenName,node,path){if(isComponentNode(node)){let props=node.props??{};for(let prop of TEXT_PROPS){let value=props[prop];if(typeof value==="string"&&consider(screenId,screenName,node,path,prop,value))break}node.children?.forEach((child,i)=>{walk(screenId,screenName,child,[...path,i])});return}if(isSnippetInstance(node)){for(let[arg,value]of Object.entries(node.args??{}))if(typeof value==="string"&&consider(screenId,screenName,node,path,arg,value))break}}for(let[screenId,screen]of folder.screens)walk(screenId,screen.name,screen.tree,[]);return{query,boards,screens,text,textTotal}}function createSearchRouter(folder){let r=new Hono2;return r.get("/",(c)=>{let q=c.req.query("q")??"",limitRaw=Number(c.req.query("limit")??"20"),limit=Number.isFinite(limitRaw)?Math.min(100,Math.max(1,Math.floor(limitRaw))):20;if(q.trim()==="")return c.json(emptySearchResult(q));return c.json(searchFolder(folder(),q,limit))}),r}import{rm as rm3}from"fs/promises";import{join as join25}from"path";var baseTypography={fontFamily:{sans:"Inter, ui-sans-serif, system-ui, sans-serif",mono:"ui-monospace, SFMono-Regular, monospace"},typesets:{default:{size:"1em",leading:1.75,flow:"1.25em",fontBody:"sans",fontMono:"mono"},docs:{size:15,leading:1.75,flow:"1.5em"},chat:{leading:1.6,flow:"1em"},compact:{size:14,leading:1.6,flow:"1em"},reading:{size:18,leading:1.9,flow:"2em"}}},baseSpacing={0:0,1:4,2:8,3:12,4:16,6:24,8:32,12:48,16:64},baseRadius={sm:4,md:6,lg:8,xl:12,full:9999},PRESETS={"default-light":{name:"default-light",colors:{background:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)",primary:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},secondary:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.205 0 0)"},muted:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.556 0 0)"},accent:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.205 0 0)"},destructive:{DEFAULT:"oklch(0.577 0.245 27.325)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},popover:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},border:"oklch(0.922 0 0)",input:"oklch(0.922 0 0)",ring:"oklch(0.708 0 0)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},"default-dark":{name:"default-dark",colors:{background:"oklch(0.145 0 0)",foreground:"oklch(0.985 0 0)",primary:{DEFAULT:"oklch(0.985 0 0)",foreground:"oklch(0.205 0 0)"},secondary:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.985 0 0)"},muted:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.708 0 0)"},accent:{DEFAULT:"oklch(0.269 0 0)",foreground:"oklch(0.985 0 0)"},destructive:{DEFAULT:"oklch(0.396 0.141 25.723)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},popover:{DEFAULT:"oklch(0.205 0 0)",foreground:"oklch(0.985 0 0)"},border:"oklch(0.269 0 0)",input:"oklch(0.269 0 0)",ring:"oklch(0.439 0 0)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},violet:{name:"violet",colors:{background:"oklch(0.985 0.012 290)",foreground:"oklch(0.22 0.04 290)",primary:{DEFAULT:"oklch(0.55 0.22 290)",foreground:"oklch(0.985 0.01 290)"},secondary:{DEFAULT:"oklch(0.94 0.04 290)",foreground:"oklch(0.222 0.05 290)"},muted:{DEFAULT:"oklch(0.95 0.03 290)",foreground:"oklch(0.5 0.05 290)"},accent:{DEFAULT:"oklch(0.9 0.08 290)",foreground:"oklch(0.222 0.05 290)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 290)",foreground:"oklch(0.22 0.04 290)"},popover:{DEFAULT:"oklch(0.99 0.01 290)",foreground:"oklch(0.22 0.04 290)"},border:"oklch(0.9 0.04 290)",input:"oklch(0.9 0.04 290)",ring:"oklch(0.55 0.22 290)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},emerald:{name:"emerald",colors:{background:"oklch(0.985 0.012 155)",foreground:"oklch(0.22 0.04 155)",primary:{DEFAULT:"oklch(0.5 0.18 155)",foreground:"oklch(0.985 0.01 155)"},secondary:{DEFAULT:"oklch(0.94 0.04 155)",foreground:"oklch(0.222 0.05 155)"},muted:{DEFAULT:"oklch(0.95 0.03 155)",foreground:"oklch(0.5 0.05 155)"},accent:{DEFAULT:"oklch(0.9 0.09 155)",foreground:"oklch(0.222 0.05 155)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 155)",foreground:"oklch(0.22 0.04 155)"},popover:{DEFAULT:"oklch(0.99 0.01 155)",foreground:"oklch(0.22 0.04 155)"},border:"oklch(0.9 0.04 155)",input:"oklch(0.9 0.04 155)",ring:"oklch(0.5 0.18 155)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},amber:{name:"amber",colors:{background:"oklch(0.97 0.025 80)",foreground:"oklch(0.22 0.05 50)",primary:{DEFAULT:"oklch(0.72 0.18 70)",foreground:"oklch(0.18 0.04 50)"},secondary:{DEFAULT:"oklch(0.92 0.06 75)",foreground:"oklch(0.22 0.05 50)"},muted:{DEFAULT:"oklch(0.93 0.04 75)",foreground:"oklch(0.5 0.05 50)"},accent:{DEFAULT:"oklch(0.88 0.12 75)",foreground:"oklch(0.22 0.05 50)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.02 80)",foreground:"oklch(0.22 0.05 50)"},popover:{DEFAULT:"oklch(0.99 0.02 80)",foreground:"oklch(0.22 0.05 50)"},border:"oklch(0.88 0.05 75)",input:"oklch(0.88 0.05 75)",ring:"oklch(0.72 0.18 70)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},rose:{name:"rose",colors:{background:"oklch(0.985 0.012 17)",foreground:"oklch(0.22 0.04 17)",primary:{DEFAULT:"oklch(0.585 0.197 17)",foreground:"oklch(0.99 0.005 17)"},secondary:{DEFAULT:"oklch(0.94 0.04 17)",foreground:"oklch(0.222 0.04 17)"},muted:{DEFAULT:"oklch(0.95 0.03 17)",foreground:"oklch(0.5 0.04 17)"},accent:{DEFAULT:"oklch(0.9 0.07 17)",foreground:"oklch(0.222 0.04 17)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 17)",foreground:"oklch(0.22 0.04 17)"},popover:{DEFAULT:"oklch(0.99 0.01 17)",foreground:"oklch(0.22 0.04 17)"},border:"oklch(0.9 0.04 17)",input:"oklch(0.9 0.04 17)",ring:"oklch(0.585 0.197 17)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},indigo:{name:"indigo",colors:{background:"oklch(0.99 0.005 265)",foreground:"oklch(0.22 0.04 265)",primary:{DEFAULT:"oklch(0.52 0.18 265)",foreground:"oklch(0.985 0.01 265)"},secondary:{DEFAULT:"oklch(0.95 0.025 265)",foreground:"oklch(0.22 0.04 265)"},muted:{DEFAULT:"oklch(0.96 0.02 265)",foreground:"oklch(0.5 0.04 265)"},accent:{DEFAULT:"oklch(0.9 0.08 265)",foreground:"oklch(0.22 0.04 265)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 265)",foreground:"oklch(0.22 0.04 265)"},popover:{DEFAULT:"oklch(0.99 0.01 265)",foreground:"oklch(0.22 0.04 265)"},border:"oklch(0.9 0.04 265)",input:"oklch(0.9 0.04 265)",ring:"oklch(0.52 0.18 265)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},ocean:{name:"ocean",colors:{background:"oklch(0.985 0.013 220)",foreground:"oklch(0.21 0.04 220)",primary:{DEFAULT:"oklch(0.55 0.14 220)",foreground:"oklch(0.985 0.01 220)"},secondary:{DEFAULT:"oklch(0.94 0.03 220)",foreground:"oklch(0.22 0.04 220)"},muted:{DEFAULT:"oklch(0.95 0.025 220)",foreground:"oklch(0.5 0.04 220)"},accent:{DEFAULT:"oklch(0.86 0.1 220)",foreground:"oklch(0.21 0.05 220)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 220)",foreground:"oklch(0.21 0.04 220)"},popover:{DEFAULT:"oklch(0.99 0.01 220)",foreground:"oklch(0.21 0.04 220)"},border:"oklch(0.89 0.03 220)",input:"oklch(0.89 0.03 220)",ring:"oklch(0.55 0.14 220)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},slate:{name:"slate",colors:{background:"oklch(0.985 0.005 250)",foreground:"oklch(0.205 0.02 250)",primary:{DEFAULT:"oklch(0.35 0.04 250)",foreground:"oklch(0.985 0 0)"},secondary:{DEFAULT:"oklch(0.94 0.01 250)",foreground:"oklch(0.205 0.02 250)"},muted:{DEFAULT:"oklch(0.95 0.008 250)",foreground:"oklch(0.5 0.02 250)"},accent:{DEFAULT:"oklch(0.88 0.015 250)",foreground:"oklch(0.205 0.02 250)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0 0)",foreground:"oklch(0.205 0.02 250)"},popover:{DEFAULT:"oklch(0.99 0 0)",foreground:"oklch(0.205 0.02 250)"},border:"oklch(0.9 0.01 250)",input:"oklch(0.9 0.01 250)",ring:"oklch(0.5 0.03 250)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},forest:{name:"forest",colors:{background:"oklch(0.985 0.015 145)",foreground:"oklch(0.21 0.04 145)",primary:{DEFAULT:"oklch(0.42 0.12 145)",foreground:"oklch(0.985 0.01 145)"},secondary:{DEFAULT:"oklch(0.92 0.04 145)",foreground:"oklch(0.21 0.05 145)"},muted:{DEFAULT:"oklch(0.94 0.03 145)",foreground:"oklch(0.5 0.05 145)"},accent:{DEFAULT:"oklch(0.86 0.1 145)",foreground:"oklch(0.21 0.05 145)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 145)",foreground:"oklch(0.21 0.04 145)"},popover:{DEFAULT:"oklch(0.99 0.01 145)",foreground:"oklch(0.21 0.04 145)"},border:"oklch(0.88 0.04 145)",input:"oklch(0.88 0.04 145)",ring:"oklch(0.42 0.12 145)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},sunset:{name:"sunset",colors:{background:"oklch(0.97 0.022 50)",foreground:"oklch(0.21 0.04 35)",primary:{DEFAULT:"oklch(0.62 0.2 35)",foreground:"oklch(0.985 0.01 35)"},secondary:{DEFAULT:"oklch(0.93 0.06 50)",foreground:"oklch(0.21 0.04 35)"},muted:{DEFAULT:"oklch(0.94 0.04 50)",foreground:"oklch(0.5 0.05 35)"},accent:{DEFAULT:"oklch(0.88 0.12 50)",foreground:"oklch(0.21 0.05 35)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.015 50)",foreground:"oklch(0.21 0.04 35)"},popover:{DEFAULT:"oklch(0.99 0.015 50)",foreground:"oklch(0.21 0.04 35)"},border:"oklch(0.89 0.05 50)",input:"oklch(0.89 0.05 50)",ring:"oklch(0.62 0.2 35)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius},plum:{name:"plum",colors:{background:"oklch(0.985 0.013 330)",foreground:"oklch(0.22 0.04 330)",primary:{DEFAULT:"oklch(0.45 0.16 330)",foreground:"oklch(0.985 0.01 330)"},secondary:{DEFAULT:"oklch(0.94 0.03 330)",foreground:"oklch(0.22 0.04 330)"},muted:{DEFAULT:"oklch(0.95 0.025 330)",foreground:"oklch(0.5 0.04 330)"},accent:{DEFAULT:"oklch(0.88 0.08 330)",foreground:"oklch(0.22 0.04 330)"},destructive:{DEFAULT:"oklch(0.577 0.245 27)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(0.99 0.01 330)",foreground:"oklch(0.22 0.04 330)"},popover:{DEFAULT:"oklch(0.99 0.01 330)",foreground:"oklch(0.22 0.04 330)"},border:"oklch(0.9 0.04 330)",input:"oklch(0.9 0.04 330)",ring:"oklch(0.45 0.16 330)"},typography:baseTypography,spacing:baseSpacing,radius:baseRadius}},PRESET_NAMES=Object.keys(PRESETS);async function applyPreset(folder,presetName,themeName="default"){let preset=PRESETS[presetName];if(!preset)return err(unknownPreset(presetName,`Known presets: ${Object.keys(PRESETS).join(", ")}`));return ok(await persistNamedTheme(folder,themeName,{...preset}))}import{join as join24}from"path";async function setCustomCss(folder,css){return await writeText(join24(folder.root,"theme","custom.css"),css),folder.customCss=css,ok({css,bytes:Buffer.byteLength(css,"utf8")})}var toOklch=converter_default("oklch");function r3(n,p=3){if(n===void 0||Number.isNaN(n))return 0;let k=10**p;return Math.round(n*k)/k}function formatOklch(o){let l=r3(o.l),c=r3(o.c),h=r3(o.h),a=o.alpha!==void 0&&o.alpha<1?` / ${r3(o.alpha)}`:"";return`oklch(${l} ${c} ${h}${a})`}function parseOklch2(input){let parsed=parse_default(input);if(!parsed)return err(invalidColor(`Could not parse color: ${JSON.stringify(input)}`,"Accepts #rrggbb, named colors, rgb(), hsl(), oklch(), etc."));let o=toOklch(parsed);return ok({mode:"oklch",l:o.l??0.5,c:o.c??0,h:o.h,alpha:o.alpha})}function parseOklchOrZero(input){let parsed=parse_default(input);if(!parsed)return{mode:"oklch",l:0.5,c:0};let o=toOklch(parsed);return{mode:"oklch",l:o.l??0.5,c:o.c??0,h:o.h,alpha:o.alpha}}function withL(seed,l){return formatOklch({...seed,l})}function lowChroma(seed,l,factor=0.05){return formatOklch({...seed,l,c:seed.c*factor})}function ensureContrast(fg,bg,minRatio=4.5){if(contrast(fg,bg)>=minRatio)return{color:fg,adjusted:!1};let fgOk=parseOklchOrZero(fg),bgOk=parseOklchOrZero(bg),goingDark=fgOk.l<=bgOk.l,current=fgOk;for(let step=0;step<20;step++){current={...current,l:goingDark?Math.max(0,current.l-0.05):Math.min(1,current.l+0.05)};let next=formatOklch(current);if(contrast(next,bg)>=minRatio)return{color:next,adjusted:!0};if(current.l<=0.02||current.l>=0.98)break}return{color:goingDark?"oklch(0 0 0)":"oklch(1 0 0)",adjusted:!0}}function derivePalette(seedColor,current,name){let seedR=parseOklch2(seedColor);if(!seedR.ok)return seedR;let seed=seedR.value,adjustments=[],background="oklch(1 0 0)",foreground="oklch(0.145 0 0)",primaryDefault=withL(seed,0.55),rawPrimaryFg="oklch(0.985 0 0)",primaryFg=ensureContrast(rawPrimaryFg,primaryDefault);if(primaryFg.adjusted)adjustments.push({slot:"primary.foreground",from:rawPrimaryFg,to:primaryFg.color});let secondaryDefault=lowChroma(seed,0.95,0.1),secondaryFg=ensureContrast("oklch(0.2 0 0)",secondaryDefault);if(secondaryFg.adjusted)adjustments.push({slot:"secondary.foreground",from:"oklch(0.2 0 0)",to:secondaryFg.color});let accentDefault=lowChroma(seed,0.93,0.18),accentFg=ensureContrast("oklch(0.2 0 0)",accentDefault);if(accentFg.adjusted)adjustments.push({slot:"accent.foreground",from:"oklch(0.2 0 0)",to:accentFg.color});let colors={background,foreground,primary:{DEFAULT:primaryDefault,foreground:primaryFg.color},secondary:{DEFAULT:secondaryDefault,foreground:secondaryFg.color},muted:{DEFAULT:"oklch(0.97 0 0)",foreground:"oklch(0.556 0 0)"},accent:{DEFAULT:accentDefault,foreground:accentFg.color},destructive:{DEFAULT:"oklch(0.577 0.245 27.325)",foreground:"oklch(0.985 0 0)"},card:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},popover:{DEFAULT:"oklch(1 0 0)",foreground:"oklch(0.145 0 0)"},border:"oklch(0.922 0 0)",input:"oklch(0.922 0 0)",ring:lowChroma(seed,0.708,0.5)},darkFg="oklch(0.985 0 0)",darkPrimary=withL(seed,0.72),darkPrimaryFg=ensureContrast("oklch(0.16 0 0)",darkPrimary);if(darkPrimaryFg.adjusted)adjustments.push({slot:"dark:primary.foreground",from:"oklch(0.16 0 0)",to:darkPrimaryFg.color});let darkAccent=lowChroma(seed,0.3,0.6),darkAccentFg=ensureContrast(darkFg,darkAccent);if(darkAccentFg.adjusted)adjustments.push({slot:"dark:accent.foreground",from:darkFg,to:darkAccentFg.color});let darkDestructive="oklch(0.62 0.21 25)",darkDestructiveFg=ensureContrast(darkFg,darkDestructive),colorsDark={background:lowChroma(seed,0.16,0.3),foreground:darkFg,primary:{DEFAULT:darkPrimary,foreground:darkPrimaryFg.color},secondary:{DEFAULT:lowChroma(seed,0.27,0.35),foreground:darkFg},muted:{DEFAULT:lowChroma(seed,0.27,0.35),foreground:"oklch(0.708 0 0)"},accent:{DEFAULT:darkAccent,foreground:darkAccentFg.color},destructive:{DEFAULT:darkDestructive,foreground:darkDestructiveFg.color},card:{DEFAULT:lowChroma(seed,0.205,0.3),foreground:darkFg},popover:{DEFAULT:lowChroma(seed,0.205,0.3),foreground:darkFg},border:"oklch(1 0 0 / 0.1)",input:"oklch(1 0 0 / 0.15)",ring:darkPrimary};return ok({theme:{...current,name:name??current.name,colors,colorsDark},adjustments})}function pairEntries(prefix,value){if(typeof value==="string")return[[prefix,value]];let out=[[`${prefix}.DEFAULT`,value.DEFAULT]];if(value.foreground!==void 0)out.push([`${prefix}.foreground`,value.foreground]);return out}function tokenAt(theme,path){let cursor=theme;for(let seg of path.split(".")){if(typeof cursor!=="object"||cursor===null)return null;cursor=cursor[seg]}if(typeof cursor==="string")return cursor;if(typeof cursor==="number")return String(cursor);if(typeof cursor==="object"&&cursor!==null&&"DEFAULT"in cursor){let d=cursor.DEFAULT;return typeof d==="string"?d:null}return null}async function importThemeCss(folder,css,opts={}){let parsed=parseThemeCss(css),extend=opts.tailwindConfig?parseThemeExtend(opts.tailwindConfig):null,container=opts.tailwindConfig?parseTailwindContainer(opts.tailwindConfig):null;if(!(Object.keys(parsed.colors).length>0||Object.keys(parsed.colorsDark).length>0||Object.keys(parsed.palette).length>0||Object.keys(parsed.paletteDark).length>0||parsed.radius!==void 0||parsed.fontFamily!==void 0||extend!==null||container!==null))return err(themeBadRequest("no recognizable theme tokens in the CSS \u2014 expected shadcn-convention custom properties (`--background`, `--primary`, \u2026) in `:root`/`.dark`, or Tailwind v4 `--color-*` vars in `@theme`"));let current=themeByName(folder,opts.themeName??"default"),next=JSON.parse(JSON.stringify(current)),warnings=[...parsed.warnings],changes=[],record=(token,to)=>{let from=tokenAt(current,token);if(from!==to)changes.push({token,from,to})},semanticSlots=new Set,paletteNames=new Set;for(let[slot,value]of Object.entries(parsed.colors)){if(value===void 0)continue;semanticSlots.add(slot),next.colors[slot]=value;for(let[token,to]of pairEntries(`colors.${slot}`,value))record(token,to)}if(Object.keys(parsed.colorsDark).length>0){next.colorsDark={...next.colorsDark??{}};for(let[slot,value]of Object.entries(parsed.colorsDark)){if(value===void 0)continue;semanticSlots.add(slot),next.colorsDark[slot]=value;for(let[token,to]of pairEntries(`colorsDark.${slot}`,value))record(token,to)}}if(Object.keys(parsed.palette).length>0){next.palette={...next.palette??{}};for(let[name,value]of Object.entries(parsed.palette))paletteNames.add(name),next.palette[name]=value,record(`palette.${name}`,value)}if(Object.keys(parsed.paletteDark).length>0){next.paletteDark={...next.paletteDark??{}};for(let[name,value]of Object.entries(parsed.paletteDark))paletteNames.add(name),next.paletteDark[name]=value,record(`paletteDark.${name}`,value)}if(parsed.radius!==void 0)next.radius={...next.radius,md:parsed.radius},record("radius.md",parsed.radius);if(parsed.fontFamily!==void 0){next.typography={...next.typography,fontFamily:{...next.typography.fontFamily??{},...parsed.fontFamily}};for(let[role,stack]of Object.entries(parsed.fontFamily))record(`typography.fontFamily.${role}`,stack)}if(extend){if(extend.colors){next.palette={...next.palette??{}};for(let[name,value]of Object.entries(extend.colors)){if(paletteName(name)===null)continue;if(name in next.palette)continue;let resolved=value.includes("var(")?resolveCssVars(value,parsed.rootVars):value;if(resolved===null||resolved===""){warnings.push(`tailwind.config color "${name}" references a CSS var not declared in the imported stylesheet \u2014 skipped`);continue}paletteNames.add(name),next.palette[name]=resolved,record(`palette.${name}`,resolved)}}if(extend.spacing){next.spacing={...next.spacing};for(let[name,value]of Object.entries(extend.spacing)){if(name in next.spacing)continue;next.spacing[name]=value,record(`spacing.${name}`,value)}}if(extend.fontFamily){let fontFamily={...next.typography.fontFamily??{}};for(let[role,stack]of Object.entries(extend.fontFamily)){if(role in fontFamily)continue;fontFamily[role]=stack,record(`typography.fontFamily.${role}`,stack)}next.typography={...next.typography,fontFamily}}if(extend.boxShadow){next.shadows={...next.shadows??{}};for(let[name,value]of Object.entries(extend.boxShadow))next.shadows[name]=value,record(`shadows.${name}`,value)}if(extend.keyframes){next.keyframes={...next.keyframes??{},...extend.keyframes};for(let[name,steps]of Object.entries(extend.keyframes))record(`keyframes.${name}`,Object.keys(steps).join(", "))}if(extend.animation){next.animation={...next.animation??{},...extend.animation};for(let[name,value]of Object.entries(extend.animation))record(`animation.${name}`,value)}}if(container){if(next.container={...next.container??{},...container},container.center!==void 0)record("container.center",String(container.center));if(container.padding!==void 0)record("container.padding",container.padding);if(container.maxWidth!==void 0)record("container.maxWidth",container.maxWidth)}let semanticTotal=Object.keys(ColorsSchema.shape).length,coverage={semantic:semanticSlots.size,semanticTotal,palette:paletteNames.size,unmapped:Object.keys(ColorsSchema.shape).filter((slot)=>!semanticSlots.has(slot))};if(semanticSlots.size===0&&paletteNames.size>0)warnings.push(`none of the ${semanticTotal} semantic color slots matched \u2014 all ${paletteNames.size} imported colors landed in \`palette.*\`, which does NOT theme the canvas. `+"This app names its custom properties on its own convention rather than shadcn's (`--background`, `--primary`, \u2026), so the design will still render on the previous palette. "+'Map the slots yourself with set_theme { tokens: { "colors.background": "<a palette value>", \u2026 } }.');let validated=ThemeSchema.safeParse(next);if(!validated.success){let issue=validated.error.issues[0];return err(invalidThemePath(`imported tokens produced an invalid theme at "${issue?.path.join(".")}": ${issue?.message??"schema mismatch"}`))}if(!opts.apply)return ok({theme:validated.data,changes,coverage,warnings,applied:!1});let persisted=await persistNamedTheme(folder,opts.themeName??"default",validated.data);return ok({theme:persisted,changes,coverage,warnings,applied:!0})}var DEFAULT_FALLBACK={serif:"ui-serif, Georgia, serif",mono:"ui-monospace, SFMono-Regular, monospace"};function familyParam(family){return family.trim().replace(/ +/g,"+")}function leadFamily(stack){return/^\s*(?:"([^"]+)"|'([^']+)'|([^,]+))/.exec(stack)?.slice(1).find(Boolean)?.trim()}var FACE_FIELDS=["fontBody","fontHeading","fontMono"];function typesetsUsing(theme,role){return Object.entries(theme.typography.typesets??{}).filter(([,typeset])=>FACE_FIELDS.some((field)=>typeset[field]===role)).map(([name])=>name)}function pruneGoogleFonts(entries,fontFamily){let declared=new Set(Object.values(fontFamily).map(leadFamily).filter((f)=>f!==void 0).map(familyParam)),seen=new Set;return entries.filter((entry)=>{let family=familyParam(entry.split(":")[0]??"");if(!declared.has(family)||seen.has(family))return!1;return seen.add(family),!0})}async function setFonts(folder,fonts,themeName="default"){let base=themeByName(folder,themeName),fontFamily={...base.typography.fontFamily??{}},googleFonts=[...base.typography.googleFonts??[]];for(let spec of fonts){if(!/^[a-z][a-z0-9-]*$/.test(spec.role))return err(invalidThemePath(`font role "${spec.role}" must be utility-name safe (lowercase letters, digits, dashes)`));if(spec.remove){if(!(spec.role in fontFamily))return err(invalidThemePath(`font role "${spec.role}" does not exist, so it cannot be removed`));let used=typesetsUsing(base,spec.role);if(used.length>0)return err(invalidThemePath(`font role "${spec.role}" is still used by the ${used.join(", ")} typeset${used.length>1?"s":""} \u2014 point ${used.length>1?"those faces":"that face"} elsewhere first`));delete fontFamily[spec.role];continue}if(!spec.family)return err(invalidThemePath(`font role "${spec.role}": family is required`));let fallback=spec.fallback??DEFAULT_FALLBACK[spec.role]??"ui-sans-serif, system-ui, sans-serif";if(fontFamily[spec.role]=`"${spec.family}", ${fallback}`,spec.google){let param=familyParam(spec.family),entry=spec.google===!0?param:`${param}:${spec.google}`,existing=googleFonts.findIndex((g)=>familyParam(g.split(":")[0]??"")===param);if(existing===-1)googleFonts.push(entry);else googleFonts[existing]=entry}}let next={...base,typography:{...base.typography,fontFamily,googleFonts:pruneGoogleFonts(googleFonts,fontFamily)}},parsed=ThemeSchema.safeParse(next);if(!parsed.success)return err(invalidThemePath(parsed.error.issues[0]?.message??"invalid theme"));return{ok:!0,value:await persistNamedTheme(folder,themeName,parsed.data)}}function applyPath(target,path,value){if(!path)return"path is required";let segments=path.split(".");if(segments.some((s)=>s===""||s==="__proto__"||s==="constructor"||s==="prototype"))return`bad path: ${JSON.stringify(path)}`;let cursor=target;for(let i=0;i<segments.length-1;i++){let k=segments[i];if(k==="__proto__"||k==="constructor"||k==="prototype")return`bad path: ${JSON.stringify(path)}`;let existing=cursor[k];if(existing===void 0||typeof existing!=="object"||existing===null)cursor[k]={};cursor=cursor[k]}return cursor[segments[segments.length-1]]=value,null}function valueAt(source,path){let cursor=source;for(let segment of path.split(".")){if(cursor===null||typeof cursor!=="object")return;cursor=cursor[segment]}return cursor}async function setTokens(folder,entries,themeName="default"){let working=JSON.parse(JSON.stringify(themeByName(folder,themeName))),applied=[],failed=[];for(let{path,value}of entries){let candidate=JSON.parse(JSON.stringify(working)),pathError=applyPath(candidate,path,value);if(pathError!==null){failed.push({path,reason:pathError});continue}let parsed=ThemeSchema.safeParse(candidate);if(!parsed.success){let issue=parsed.error.issues[0],where=issue?.path.join(".")||path;failed.push({path,reason:`Setting ${path} to ${JSON.stringify(value)} produced an invalid theme at "${where}": ${issue?.message??"schema mismatch"}`});continue}if(valueAt(parsed.data,path)===void 0){failed.push({path,reason:`"${path}" is not a token this theme defines, so the write was dropped. Check the slot name (e.g. "colors.primary.DEFAULT", "colorsDark.background"), or use "palette.<name>" for a raw brand value.`});continue}working=parsed.data,applied.push(path)}if(failed.length>0)return err({kind:"BulkTokensInvalid",applied,failed});let finalTheme=working,coalesceKey=`token:${applied.join(",")}`,persisted=await tryCatchAsync(()=>persistNamedTheme(folder,themeName,finalTheme,coalesceKey),(e)=>invalidThemePath(`Persisting theme "${themeName}" failed: ${e.message}`));return persisted.ok?ok({theme:persisted.value,applied}):persisted}async function setToken(folder,path,value,themeName="default"){let r=await setTokens(folder,[{path,value}],themeName);if(r.ok)return ok(r.value.theme);if(r.error.kind==="BulkTokensInvalid")return err(invalidThemePath(r.error.failed[0]?.reason??"invalid token"));return r}var FACE_FIELDS2=["fontBody","fontHeading","fontMono"];function merge2(current,spec){let next={...current};if(spec.size!==void 0)if(spec.size===null)delete next.size;else next.size=spec.size;if(spec.leading!==void 0)if(spec.leading===null)delete next.leading;else next.leading=spec.leading;if(spec.flow!==void 0)if(spec.flow===null)delete next.flow;else next.flow=spec.flow;for(let field of FACE_FIELDS2){let value=spec[field];if(value===void 0)continue;if(value===null)delete next[field];else next[field]=value}return next}async function setTypeset(folder,typesets,themeName="default"){let base=themeByName(folder,themeName),declaredFaces=base.typography.fontFamily??{},next={...base.typography.typesets??{}};for(let spec of typesets){let name=spec.name??DEFAULT_TYPESET_NAME;if(!isTypesetName(name))return err(invalidThemePath(`typeset name "${name}" must be selector-safe (letters, digits, dash, underscore) \u2014 it is emitted as a \`.typeset-${name}\` class`));if(spec.remove&&spec.renameTo!==void 0)return err(invalidThemePath(`typeset "${name}": pass either remove or renameTo, not both`));if(spec.remove||spec.renameTo!==void 0){let verb=spec.remove?"removed":"renamed";if(name===DEFAULT_TYPESET_NAME)return err(invalidThemePath(`the "${DEFAULT_TYPESET_NAME}" typeset is the baseline every preset inherits from \u2014 it cannot be ${verb}`));if(!(name in next))return err(invalidThemePath(`typeset "${name}" does not exist, so it cannot be ${verb}`))}if(spec.remove){delete next[name];continue}let target=name;if(spec.renameTo!==void 0&&spec.renameTo!==name){if(!isTypesetName(spec.renameTo))return err(invalidThemePath(`typeset name "${spec.renameTo}" must be selector-safe (letters, digits, dash, underscore) \u2014 it is emitted as a \`.typeset-${spec.renameTo}\` class`));if(spec.renameTo in next)return err(invalidThemePath(`typeset "${spec.renameTo}" already exists`));next[spec.renameTo]=next[name]??{},delete next[name],target=spec.renameTo}if(spec.leading!==void 0&&spec.leading!==null&&spec.leading<=0)return err(invalidThemePath(`typeset "${name}": leading must be a positive number`));for(let field of FACE_FIELDS2){let role=spec[field];if(role===void 0||role===null)continue;if(!(role in declaredFaces)){let known=Object.keys(declaredFaces).sort().join(", ")||"none declared";return err(invalidThemePath(`typeset "${name}": ${field} "${role}" is not a declared font role (have: ${known}). Declare it with set_fonts first.`))}if(TYPESET_SCALE_NAMES.includes(role))return err(invalidThemePath(`typeset "${name}": font role "${role}" collides with the type scale \u2014 rename the role (it would make \`font-${role}\` ambiguous)`))}next[target]=merge2(next[target]??{},spec)}let candidate={...base,typography:{...base.typography,typesets:next}},parsed=ThemeSchema.safeParse(candidate);if(!parsed.success)return err(invalidThemePath(parsed.error.issues[0]?.message??"invalid theme"));return{ok:!0,value:await persistNamedTheme(folder,themeName,parsed.data,coalesceKey(typesets))}}function coalesceKey(typesets){let parts=[];for(let spec of typesets){if(spec.remove||spec.renameTo!==void 0)return;let fields=Object.keys(spec).filter((k)=>k!=="name"&&spec[k]!==void 0).sort();if(fields.length===0)return;parts.push(`${spec.name??DEFAULT_TYPESET_NAME}:${fields.join(",")}`)}return parts.length>0?`typeset:${parts.join("|")}`:void 0}function srgbChannel(c){return c<=0.03928?c/12.92:((c+0.055)/1.055)**2.4}function relativeLuminance(rgb){let r=srgbChannel(rgb.r),g=srgbChannel(rgb.g),b=srgbChannel(rgb.b);return 0.2126*r+0.7152*g+0.0722*b}function parseToRgb(color){let parsed=parse_default(color);if(!parsed)return null;let hex=formatHex(parsed);if(!hex)return null;let stripped=hex.replace(/^#/,"");if(stripped.length!==6)return null;let r=Number.parseInt(stripped.slice(0,2),16)/255,g=Number.parseInt(stripped.slice(2,4),16)/255,b=Number.parseInt(stripped.slice(4,6),16)/255;return{r,g,b}}function contrastRatio(fg,bg){let fgRgb=parseToRgb(fg),bgRgb=parseToRgb(bg);if(!fgRgb||!bgRgb)return null;let l1=relativeLuminance(fgRgb),l2=relativeLuminance(bgRgb),lighter=Math.max(l1,l2),darker=Math.min(l1,l2);return(lighter+0.05)/(darker+0.05)}function tierForRatio(ratio){if(ratio>=7)return"AAA";if(ratio>=4.5)return"AA";if(ratio>=3)return"AAlarge";return"Fail"}function colorValue2(slot){if(typeof slot==="string")return slot;if(slot&&typeof slot==="object"&&"DEFAULT"in slot){let def=slot.DEFAULT;return typeof def==="string"?def:void 0}return}function fgValue(slot){if(slot&&typeof slot==="object"&&"foreground"in slot){let fg=slot.foreground;return typeof fg==="string"?fg:void 0}return}function scoreThemeContrast(theme,mode="light"){let c=resolveColors(theme,mode==="dark"),pairs=[{label:"foreground on background",fg:c.foreground,bg:c.background},{label:"primary-foreground on primary",fg:fgValue(c.primary),bg:colorValue2(c.primary)},{label:"secondary-foreground on secondary",fg:fgValue(c.secondary),bg:colorValue2(c.secondary)},{label:"accent-foreground on accent",fg:fgValue(c.accent),bg:colorValue2(c.accent)},{label:"muted-foreground on muted",fg:fgValue(c.muted),bg:colorValue2(c.muted)},{label:"card-foreground on card",fg:fgValue(c.card),bg:colorValue2(c.card)},{label:"destructive-foreground on destructive",fg:fgValue(c.destructive),bg:colorValue2(c.destructive)},{label:"foreground on card",fg:c.foreground,bg:colorValue2(c.card)},{label:"muted-foreground on background",fg:fgValue(c.muted),bg:c.background}],out=[];for(let p of pairs){if(!p.fg||!p.bg)continue;let ratio=contrastRatio(p.fg,p.bg);if(ratio===null)continue;out.push({label:p.label,mode,fg:p.fg,bg:p.bg,ratio:Math.round(ratio*100)/100,tier:tierForRatio(ratio)})}return out}function scoreThemeContrastBoth(theme){return[...scoreThemeContrast(theme,"light"),...scoreThemeContrast(theme,"dark")]}var themeLocks=createLockMap();function withThemeLock(folder,fn){return themeLocks.run(folder.root,fn)}function broadcastThemeChanged(ctx){ctx.broadcast({type:"theme-changed"})}async function setToken2(ctx,path,value,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setToken(ctx.folder,path,value,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_token",{token:path,...themeName?{themeName}:{}});return r})}async function setTokens2(ctx,entries,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setTokens(ctx.folder,entries,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_tokens",{...entries[0]?{token:entries[0].path}:{},...themeName?{themeName}:{}});return r})}async function setFonts2(ctx,fonts,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setFonts(ctx.folder,fonts,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_fonts",themeName?{themeName}:{});return r})}async function setTypeset2(ctx,typesets,themeName){return withThemeLock(ctx.folder,async()=>{let r=await setTypeset(ctx.folder,typesets,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"set_typeset",themeName?{themeName}:{});return r})}async function addTheme(ctx,name,from,overwrite=!1){return withThemeLock(ctx.folder,async()=>{if(!/^[a-z][a-z0-9-]*$/.test(name)||name==="default")return{ok:!1,error:{kind:"InvalidThemePath",reason:`theme name "${name}" must be lowercase kebab (and not "default")`}};if(!overwrite&&ctx.folder.themes.has(name))return{ok:!1,error:{kind:"InvalidThemePath",reason:`theme "${name}" already exists (pass overwrite: true to replace)`}};let source=themeByName(ctx.folder,from),cloned=JSON.parse(JSON.stringify({...source,name})),persisted=await persistNamedTheme(ctx.folder,name,cloned);return broadcastThemeChanged(ctx),emitActivity(ctx,"add_theme",{themeName:name}),{ok:!0,value:{name,theme:persisted}}})}async function renameTheme(ctx,name,renameTo){return withThemeLock(ctx.folder,async()=>{let guard=namedThemeGuard(ctx,name,"rename");if(guard)return guard;if(!/^[a-z][a-z0-9-]*$/.test(renameTo)||renameTo==="default")return themeErr(`theme name "${renameTo}" must be lowercase kebab (and not "default")`);if(ctx.folder.themes.has(renameTo))return themeErr(`theme "${renameTo}" already exists`);let source=ctx.folder.themes.get(name);await persistNamedTheme(ctx.folder,renameTo,{...source,name:renameTo}),await rm3(join25(ctx.folder.root,"theme",`${name}.json`),{force:!0}),ctx.folder.themes.delete(name);let repointedBoards=[];for(let board of ctx.folder.boards.values()){if(board.theme!==name)continue;await persistBoard(ctx.folder,board.id,{...board,theme:renameTo}),repointedBoards.push(board.id)}return broadcastThemeChanged(ctx),emitActivity(ctx,"rename_theme",{themeName:renameTo}),{ok:!0,value:{name:renameTo,repointedBoards}}})}async function removeTheme(ctx,name){return withThemeLock(ctx.folder,async()=>{let guard=namedThemeGuard(ctx,name,"remove");if(guard)return guard;let pinned=[...ctx.folder.boards.values()].filter((b)=>b.theme===name).map((b)=>b.id);if(pinned.length>0)return themeErr(`theme "${name}" is pinned by ${pinned.join(", ")} \u2014 repoint or unpin those boards first (update_board { patch: { theme: null } })`);return await rm3(join25(ctx.folder.root,"theme",`${name}.json`),{force:!0}),ctx.folder.themes.delete(name),broadcastThemeChanged(ctx),emitActivity(ctx,"remove_theme",{themeName:name}),{ok:!0,value:{removedTheme:name}}})}var themeErr=(reason)=>({ok:!1,error:{kind:"InvalidThemePath",reason}});function namedThemeGuard(ctx,name,verb){if(name==="default")return themeErr(`cannot ${verb} the default theme`);if(!ctx.folder.themes.has(name))return themeErr(`theme "${name}" does not exist`);return null}function listThemes(ctx){return{themes:[...ctx.folder.themes.keys()].sort().map((name)=>({name,usedByBoards:[...ctx.folder.boards.values()].filter((b)=>(b.theme??"default")===name).map((b)=>b.id)}))}}async function setCustomCss2(ctx,css){return withThemeLock(ctx.folder,async()=>{let r=await setCustomCss(ctx.folder,css);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"custom_css",{});return r})}async function applyPreset2(ctx,presetName,themeName){return withThemeLock(ctx.folder,async()=>{let r=await applyPreset(ctx.folder,presetName,themeName);if(r.ok)broadcastThemeChanged(ctx),emitActivity(ctx,"apply_preset",themeName?{themeName}:{});return r})}async function derivePaletteFromColor(ctx,seedColor,name){return withThemeLock(ctx.folder,()=>DoAsync(async function*(){let result=yield*$(derivePalette(seedColor,ctx.folder.theme,name)),persisted=await persistNamedTheme(ctx.folder,name??"default",result.theme);return broadcastThemeChanged(ctx),emitActivity(ctx,"derive_palette_from_color",name?{themeName:name}:{}),{...result,theme:persisted}}))}async function importThemeCss2(ctx,css,opts={}){return withThemeLock(ctx.folder,async()=>{let r=await importThemeCss(ctx.folder,css,opts);if(r.ok&&r.value.applied)broadcastThemeChanged(ctx),emitActivity(ctx,"import_theme",opts.themeName?{themeName:opts.themeName}:{});return r})}var SetTokenBody=object({path:string().min(1),value:union([string(),number()]),theme:string().min(1).optional()}),ApplyPresetBody=object({presetName:string().min(1),theme:string().min(1).optional()}),DeriveFromColorBody=object({seedColor:string().min(1),name:string().min(1).optional()}),AddThemeBody=object({name:string().min(1),from:string().min(1).optional()}),TypesetSpecBody=object({name:string().min(1).optional(),renameTo:string().min(1).optional(),remove:boolean().optional(),size:union([string(),number(),_null()]).optional(),leading:union([number(),_null()]).optional(),flow:union([string(),number(),_null()]).optional(),fontBody:union([string(),_null()]).optional(),fontHeading:union([string(),_null()]).optional(),fontMono:union([string(),_null()]).optional()}),SetTypesetBody=object({typesets:array(TypesetSpecBody).min(1),theme:string().min(1).optional()}),FontSpecBody=object({role:string().min(1),family:string().min(1).optional(),fallback:string().min(1).optional(),google:union([string().min(1),literal(!0)]).optional(),remove:boolean().optional()}),SetFontsBody=object({fonts:array(FontSpecBody).min(1),theme:string().min(1).optional()});function createThemeRouter(ctxFor){let r=new Hono2,route=makeThemeRoute(ctxFor);return r.get("/",(c)=>c.json(themeByName(ctxFor().folder,c.req.query("name")))),r.get("/presets",(c)=>c.json({presets:PRESET_NAMES})),r.get("/preset-summaries",(c)=>{let summaries=PRESET_NAMES.map((name)=>{let t=PRESETS[name];if(!t)return null;return{name,swatches:{background:t.colors.background,primary:t.colors.primary?.DEFAULT??t.colors.primary,accent:t.colors.accent?.DEFAULT??t.colors.accent,foreground:t.colors.foreground}}}).filter((x)=>x!==null);return c.json({presets:summaries})}),r.get("/contrast",(c)=>c.json({results:scoreThemeContrast(themeByName(ctxFor().folder,c.req.query("name")))})),r.post("/set_token",route(SetTokenBody,async(args,ctx)=>map(await setToken2(ctx,args.path,args.value,args.theme),(theme)=>({theme})))),r.post("/apply_preset",route(ApplyPresetBody,async(args,ctx)=>map(await applyPreset2(ctx,args.presetName,args.theme),(theme)=>({theme})))),r.post("/set_typeset",route(SetTypesetBody,async(args,ctx)=>map(await setTypeset2(ctx,args.typesets,args.theme),(theme)=>({theme})))),r.post("/set_fonts",route(SetFontsBody,async(args,ctx)=>map(await setFonts2(ctx,args.fonts,args.theme),(theme)=>({theme})))),r.post("/add_theme",route(AddThemeBody,(args,ctx)=>addTheme(ctx,args.name,args.from))),r.post("/derive_palette_from_color",route(DeriveFromColorBody,(args,ctx)=>derivePaletteFromColor(ctx,args.seedColor,args.name))),r}import{rm as rm4}from"fs/promises";import{join as join26}from"path";function createUndoRouter(folderFor,broadcast){let r=new Hono2;return r.get("/",(c)=>c.json(folderFor().history.depths())),r.post("/",async(c)=>{let folder=folderFor(),entry=folder.history.popUndo();if(!entry)return c.json({reverted:null,...folder.history.depths()});return c.json({reverted:await applyRevert(entry,folder,broadcast,"redo"),...folder.history.depths()})}),r.post("/redo",async(c)=>{let folder=folderFor(),entry=folder.history.popRedo();if(!entry)return c.json({reverted:null,...folder.history.depths()});return c.json({reverted:await applyRevert(entry,folder,broadcast,"undo"),...folder.history.depths()})}),r}async function applyRevert(entry,folder,broadcast,pushOpposite){if(entry.kind==="screen"){let current=folder.screens.get(entry.screenId)??null,back={kind:"screen",screenId:entry.screenId,screen:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withScreenLock(folder,entry.screenId,async()=>{if(entry.screen===null)await deleteScreen(folder,entry.screenId);else await writeScreen(folder,entry.screenId,entry.screen)}),broadcast({type:"screen-changed",screenId:entry.screenId}),{kind:"screen",screenId:entry.screenId}}if(entry.kind==="board"){let current=folder.boards.get(entry.boardId)??null,back={kind:"board",boardId:entry.boardId,board:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withBoardLock(folder,entry.boardId,async()=>{if(entry.board===null)await deleteBoard(folder,entry.boardId);else await writeBoard(folder,entry.boardId,entry.board)}),broadcast({type:"board-changed",boardId:entry.boardId}),{kind:"board",boardId:entry.boardId}}if(entry.kind==="boards"){let boardIds=entry.boards.map((b)=>b.boardId),back={kind:"boards",boards:boardIds.map((boardId)=>({boardId,board:folder.boards.get(boardId)??null}))};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);await withBoardLocks(folder,boardIds,async()=>{for(let{boardId,board}of[...entry.boards].reverse())if(board===null)await deleteBoard(folder,boardId);else await writeBoard(folder,boardId,board)});for(let boardId of boardIds)broadcast({type:"board-changed",boardId});return{kind:"boards",boardIds}}if(entry.kind==="snippet"){let current=folder.snippets.get(entry.snippetId)??null,back={kind:"snippet",snippetId:entry.snippetId,snippet:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await withSnippetLock(folder,entry.snippetId,async()=>{if(entry.snippet===null)await deleteSnippet(folder,entry.snippetId);else await writeSnippet(folder,entry.snippetId,entry.snippet)}),broadcast({type:"snippet-changed",snippetId:entry.snippetId}),{kind:"snippet",snippetId:entry.snippetId}}let name=entry.themeName,current=name==="default"?folder.theme:folder.themes.get(name)??null,back={kind:"theme",themeName:name,theme:current};if(pushOpposite==="redo")folder.history.pushRedo(back);else folder.history.pushUndoSilent(back);return await writeTheme(folder,name,entry.theme),broadcast({type:"theme-changed"}),{kind:"theme",themeName:name}}async function writeScreen(folder,screenId,screen){await writeJsonAtomic(join26(folder.root,"screens",`${screenId}.json`),screen),folder.screens.set(screenId,screen)}async function deleteScreen(folder,screenId){await rm4(join26(folder.root,"screens",`${screenId}.json`),{force:!0}),folder.screens.delete(screenId)}async function writeBoard(folder,boardId,board){await writeJsonAtomic(join26(folder.root,"boards",`${boardId}.json`),board),folder.boards.set(boardId,board)}async function deleteBoard(folder,boardId){await rm4(join26(folder.root,"boards",`${boardId}.json`),{force:!0}),folder.boards.delete(boardId)}async function writeTheme(folder,name,theme){let path=join26(folder.root,"theme",`${name}.json`);if(theme===null&&name!=="default"){await rm4(path,{force:!0}),folder.themes.delete(name);return}if(theme===null)return;if(await writeJsonAtomic(path,theme),folder.themes.set(name,theme),name==="default")folder.theme=theme}async function writeSnippet(folder,snippetId,snippet){await writeJsonAtomic(join26(folder.root,"snippets",`${snippetId}.json`),snippet),folder.snippets.set(snippetId,snippet)}async function deleteSnippet(folder,snippetId){await rm4(join26(folder.root,"snippets",`${snippetId}.json`),{force:!0}),folder.snippets.delete(snippetId)}var UPDATES_UNAVAILABLE={current:"unknown",latest:null,available:!1,method:"unknown",channel:"unknown",upgradable:!1,reason:"This canvas was not started by the velloo CLI, so it cannot update itself."};function createUpdatesRouter(updates){let app=new Hono2,inFlight=null;return app.get("/status",async(c)=>{if(!updates)return c.json(UPDATES_UNAVAILABLE);let refresh=c.req.query("refresh")==="1";return c.json(await updates.status({refresh}))}),app.post("/",async(c)=>{if(!updates)return c.json({error:{kind:"unavailable",message:UPDATES_UNAVAILABLE.reason}},503);if(inFlight)return c.json({error:{kind:"busy",message:"An upgrade is already running."}},409);let run=updates.upgrade();inFlight=run;try{return c.json(await run)}catch(error){return c.json({error:{kind:"upgrade_failed",message:error instanceof Error?error.message:String(error)}},500)}finally{inFlight=null}}),app}var LOOPBACK_HOSTS=new Set(["localhost","127.0.0.1","[::1]","::1"]);function hostHeaderHostname(host){if(!host)return null;if(host.startsWith("[")){let end=host.indexOf("]");return end===-1?host:host.slice(0,end+1)}let colon=host.lastIndexOf(":");return colon===-1?host:host.slice(0,colon)}function originHostname(origin){if(origin==null)return null;try{return new URL(origin).hostname}catch{return null}}function hostIsLoopback(host){let hostname=hostHeaderHostname(host);return hostname!==null&&LOOPBACK_HOSTS.has(hostname)}function requestIsLocal(headers){if(!hostIsLoopback(headers.host))return!1;if(headers.origin!=null){let origin=originHostname(headers.origin);if(!origin||!LOOPBACK_HOSTS.has(origin))return!1}return!0}function localOnlyMiddleware(){return async(c,next)=>{if(!requestIsLocal({host:new URL(c.req.url).host,origin:c.req.header("origin")}))return c.json({error:"forbidden: request is not from a local origin"},403);return next()}}function createApp(ctxFor,jit,bundler,canvasBundler,auth,publish,cloud,comments,updates){let app=new Hono2,folder=()=>ctxFor().folder;return app.use("*",localOnlyMiddleware()),app.get("/api/health",(c)=>c.json({ok:!0,app:"velloo",root:ctxFor().folder.root,pid:process.pid})),app.get("/api/activity",(c)=>c.json({events:activityLog(ctxFor().folder.root)})),app.route("/api/design",createDesignRouter(ctxFor)),app.route("/api/config",createConfigRouter(ctxFor)),app.route("/api/screen",createScreenRouter(folder)),app.route("/api/board",createBoardRouter(folder)),app.route("/api/snippets",createSnippetsRouter(folder)),app.route("/api/search",createSearchRouter(folder)),app.route("/api/captures",createCapturesRouter(folder)),app.route("/api/comments",createCommentsRouter(comments??new LocalCommentsService(ctxFor))),app.route("/api/assets",createAssetsRouter(folder,cloud)),app.route("/api/render",createRenderRouter(ctxFor,jit,bundler,canvasBundler)),app.route("/api/export",createExportRouter(ctxFor,jit,bundler,canvasBundler)),app.route("/api/preflight",createPreflightRouter(ctxFor)),app.route("/api/live",createLiveRouter(bundler)),app.route("/api/canvas",createCanvasRouter(canvasBundler,()=>ctxFor().folder.config.defaultLibrary)),app.route("/api/components",createComponentsRouter(ctxFor)),app.route("/api/mutate",createMutateRouter(ctxFor)),app.route("/api/theme",createThemeRouter(ctxFor)),app.route("/api/annotations",createAnnotationsRouter(ctxFor)),app.route("/api/notes",createNotesRouter(ctxFor)),app.route("/api/auth",createAuthRouter(auth)),app.route("/api/publish",createPublishRouter(publish)),app.route("/api/updates",createUpdatesRouter(updates)),app.route("/api/undo",createUndoRouter(folder,(e)=>ctxFor().broadcast(e))),app}class Broadcaster{clients=new Set;register(ws){this.clients.add(ws)}unregister(ws){this.clients.delete(ws)}broadcast(event){let payload=JSON.stringify(event);for(let ws of this.clients)try{ws.send(payload)}catch{}}size(){return this.clients.size}}import{mkdir as mkdir9,writeFile as writeFile8}from"fs/promises";import{tmpdir as tmpdir2}from"os";import{dirname as dirname14,join as join28}from"path";import{existsSync as existsSync8}from"fs";import{dirname as dirname13,join as join27}from"path";import{fileURLToPath as fileURLToPath2}from"url";var here2=dirname13(fileURLToPath2(import.meta.url));function resolveDir2(){return[process.env.VELLOO_SCHEMA_SRC,join27(here2,"pkgs","schema","src"),here2].filter((p)=>Boolean(p)).find((d)=>existsSync8(join27(d,"typeset.ts")))??here2}var schemaSrcDir=resolveDir2();var EMPTY=`export function mountScreen() {}
|
|
220
|
+
`;async function buildCanvasBundle(hostRoot,spec,componentIds,aliases=[],minify=!1){let errors=[],runtimePaths=resolveRuntime(hostRoot,spec.styleRuntime,errors);if(!runtimePaths)return{code:EMPTY,errors,usable:!1,diagnostics:[]};let overlayIds=new Set(spec.overlayIds??[]),requested=new Set(componentIds);if([...overlayIds].some((id)=>requested.has(id)))requested.add("Paper"),requested.add("Box");let declared;try{declared=await spec.components([...requested])}catch(error){return errors.push({message:messageOf(error)}),{code:EMPTY,errors,usable:!1,diagnostics:[]}}let byId=new Map(declared.map((entry)=>[entry.id,entry])),diagnostics=[],resolved=[],preflight=new Map,plugins=[aliasPlugin(hostRoot,aliases),hostRuntimePlugin(hostRoot),vellooSourcePlugin(),...radixShimPlugin(hostRoot)??[]];for(let id of requested){if(overlayIds.has(id)){let note=byId.get(id)?.sources[0]?.note;diagnostics.push({id,status:"adapted",...note?{note}:{}});continue}let component=byId.get(id);if(!component||component.sources.length===0){diagnostics.push({id,status:"unavailable",note:"No browser-canvas source is registered for this ref, so the screen keeps its server render instead of client-mounting."});continue}let failures=[],chosen;for(let source of component.sources){let path;try{path=resolveImport(source.importPath,hostRoot,aliases)}catch(error){failures.push(`${source.importPath}: ${messageOf(error)}`);continue}if(source.preflight){let check=preflight.get(path);if(!check)check=preflightSource(path,plugins),preflight.set(path,check);let compileErrors=await check;if(compileErrors.length>0){failures.push(...compileErrors.map((error)=>`${source.importPath}: ${error}`));continue}}chosen={source,path};break}if(!chosen){let note=component.sources.at(-1)?.note;diagnostics.push({id,status:"unavailable",...note?{note}:{},...failures.length>0?{errors:failures}:{}});continue}resolved.push({id,path:chosen.path,exportName:chosen.source.exportName??id}),diagnostics.push({id,status:chosen.source.fidelity,importPath:chosen.source.importPath,...chosen.source.note?{note:chosen.source.note}:{},...failures.length>0?{errors:failures}:{}})}let unavailable=diagnostics.filter((entry)=>entry.status==="unavailable");if(unavailable.length>0||resolved.length===0){for(let entry of unavailable)errors.push({message:`${entry.id}: ${entry.errors?.join("; ")??"no browser source"}`});return{code:EMPTY,errors,usable:!1,diagnostics}}let entrySource=buildCanvasEntry({...runtimePaths,styleRuntime:spec.styleRuntime,components:resolved,overlayIds:spec.overlayIds??[],diagnostics}),key=Bun.hash(`${hostRoot}:${JSON.stringify(resolved.map((entry)=>[entry.id,entry.path,entry.exportName]))}:${JSON.stringify(spec.styleRuntime)}`).toString(16);try{let dir=join28(tmpdir2(),"velloo-canvas",key);await mkdir9(dir,{recursive:!0});let entryPath=join28(dir,"entry.tsx");await writeFile8(entryPath,entrySource,"utf8");let result=await Bun.build({entrypoints:[entryPath],target:"browser",format:"esm",minify,sourcemap:"none",define:{"process.env.NODE_ENV":'"production"'},plugins});if(!result.success){for(let log of result.logs)errors.push({message:log.message});return{code:EMPTY,errors,usable:!1,diagnostics}}let output=result.outputs[0];if(!output)return errors.push({message:"Bun.build produced no canvas-bundle artifact."}),{code:EMPTY,errors,usable:!1,diagnostics};return{code:await output.text(),errors,usable:!0,diagnostics}}catch(error){for(let item of error instanceof AggregateError?error.errors:[error])errors.push({message:messageOf(item)});return{code:EMPTY,errors,usable:!1,diagnostics}}}function resolveRuntime(hostRoot,runtime,errors){let need=(specifier)=>{try{return Bun.resolveSync(specifier,hostRoot)}catch(error){errors.push({importPath:specifier,message:messageOf(error)});return}},reactPath=need("react"),reactDomClientPath=need("react-dom/client");if(!reactPath||!reactDomClientPath)return;if(runtime.kind==="none")return{reactPath,reactDomClientPath};let emotionCachePath=need("@emotion/cache"),emotionReactPath=need("@emotion/react"),stylesPath=need(runtime.stylesModule);if(!emotionCachePath||!emotionReactPath||!stylesPath)return;return{reactPath,reactDomClientPath,emotionCachePath,emotionReactPath,stylesPath}}async function preflightSource(path,plugins){try{let result=await Bun.build({entrypoints:[path],target:"browser",format:"esm",sourcemap:"none",define:{"process.env.NODE_ENV":'"production"'},plugins});return result.success?[]:result.logs.map((log)=>log.message)}catch(error){return(error instanceof AggregateError?error.errors:[error]).map(messageOf)}}var HOST_PACKAGES=["react","react-dom","radix-ui","@radix-ui","lucide-react","class-variance-authority","clsx","tailwind-merge"];function hostRuntimePlugin(hostRoot){let available=HOST_PACKAGES.filter((name)=>{try{return Bun.resolveSync(name==="@radix-ui"?"@radix-ui/react-slot":name,hostRoot),!0}catch{return!1}});return{name:"velloo-host-packages",setup(build){if(available.length===0)return;let filter=new RegExp(`^(${available.map((name)=>name.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")).join("|")})(?:/.*)?$`);build.onResolve({filter},(args)=>{if(INSTALLED.test(args.importer)&&!HOST_SINGLETONS.has(packageName(args.path)))try{return{path:Bun.resolveSync(args.path,dirname14(args.importer))}}catch{}return{path:Bun.resolveSync(args.path,hostRoot)}})}}}var INSTALLED=/[\\/]node_modules[\\/]/,HOST_SINGLETONS=new Set(["react","react-dom"]);function packageName(specifier){let parts=specifier.split("/");return specifier.startsWith("@")?parts.slice(0,2).join("/"):parts[0]??specifier}function vellooSourcePlugin(){return{name:"velloo-owned-source",setup(build){build.onResolve({filter:/^@velloo\/helpers$/},()=>({path:join28(helpersComponentsDir,"index.ts")})),build.onResolve({filter:/^@velloo\/schema\/[a-z-]+$/},(args)=>({path:join28(schemaSrcDir,`${args.path.slice(15)}.ts`)}))}}}var RADIX_NAMESPACES=["Accordion","AlertDialog","Avatar","Checkbox","Collapsible","Dialog","DropdownMenu","Label","Popover","Progress","RadioGroup","ScrollArea","Select","Separator","Slider","Slot","Switch","Tabs","Toggle","ToggleGroup","Tooltip"];function radixShimPlugin(hostRoot){try{return Bun.resolveSync("radix-ui",hostRoot),null}catch{}let lines=[];for(let name of RADIX_NAMESPACES){let kebab=name.replace(/(?!^)([A-Z])/g,"-$1").toLowerCase();try{let path=Bun.resolveSync(`@radix-ui/react-${kebab}`,hostRoot);lines.push(`export * as ${name} from ${JSON.stringify(path)};`)}catch{}}if(lines.length===0)return null;let contents=lines.join(`
|
|
221
221
|
`);return[{name:"velloo-radix-shim",setup(build){build.onResolve({filter:/^radix-ui$/},()=>({path:"radix-ui",namespace:"velloo-radix"})),build.onLoad({filter:/.*/,namespace:"velloo-radix"},()=>({contents,loader:"js"}))}}]}function messageOf(error){return error instanceof Error?error.message:String(error)}function buildCanvasEntry(opts){let imports=opts.components.map((component,index)=>`import * as __m${index} from ${JSON.stringify(component.path)};`).join(`
|
|
222
222
|
`),registry=opts.components.map((component,index)=>` ${JSON.stringify(component.id)}: pick(__m${index}, ${JSON.stringify(component.exportName)}),`).join(`
|
|
223
223
|
`),emotionImports=opts.styleRuntime.kind==="emotion"?`import createCache from ${JSON.stringify(opts.emotionCachePath)};
|
|
@@ -434,18 +434,18 @@ A stale anchor means the original node no longer exists. Use its saved bounds an
|
|
|
434
434
|
|
|
435
435
|
\`update_comment_thread { status: "deleted" }\` is permanent \u2014 use it only when the user explicitly asks.
|
|
436
436
|
|
|
437
|
-
Canvas notes are a different thing: repo-owned board artifacts for durable design guidance, created with \`add_note\`.`}},guideUri=(slug)=>`velloo://guide/${slug}`;function registerGuideResources(mcp){for(let[slug,guide]of Object.entries(GUIDES))mcp.registerResource(`guide-${slug}`,guideUri(slug),{title:guide.title,description:guide.blurb,mimeType:"text/markdown"},async(uri)=>({contents:[{uri:uri.href,mimeType:"text/markdown",text:guide.body}]}))}var read={readOnlyHint:!0,openWorldHint:!1},readRemote={readOnlyHint:!0},create={destructiveHint:!1,openWorldHint:!1},set2={destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},destroy={idempotentHint:!0,openWorldHint:!1},remote={destructiveHint:!1},TOOL_ANNOTATIONS={find_nodes:read,get_board:read,get_capture:read,get_comment_thread:read,get_screen:read,get_snippet:read,get_theme:read,inspect:read,list_annotations:read,list_assets:read,list_boards:read,list_captures:read,list_comment_threads:read,list_components:read,component_status:read,list_notes:read,list_screens:read,list_themes:read,score_theme_contrast:read,screenshot:read,render_snippet:read,compare_to_url:readRemote,emit_code:read,emit_snippet:read,emit_theme:{idempotentHint:!0,openWorldHint:!1},compose:{openWorldHint:!1},update_props:set2,move_node:set2,set_node_id:set2,remove_node:destroy,add_screen:create,update_screen:set2,remove_screen:destroy,add_board:create,update_board:set2,remove_board:destroy,reorder_boards:set2,add_frame:create,update_frame:set2,remove_frame:destroy,update_viewport_presets:set2,add_snippet:create,update_snippet:set2,remove_snippet:destroy,update_snippet_instance:set2,add_theme:create,set_theme:set2,update_theme:set2,remove_theme:destroy,import_theme:set2,add_note:create,update_note:set2,remove_note:destroy,add_annotation:create,update_annotation:set2,remove_annotation:destroy,update_comment_thread:set2,add_extension:create,update_extension:set2,remove_extension:destroy,import_assets:set2,upload_asset:set2,generate_asset:remote,batch:{openWorldHint:!1},start_capture_session:remote,send_feedback:remote},FACADE_TOOL_ANNOTATIONS={call_velloo:{openWorldHint:!1},run_velloo_plan:{openWorldHint:!1},operation_schema:read};function applyToolPolicy(mcp){let original=mcp.registerTool.bind(mcp),patched=(name,config,cb)=>{let input=config.inputSchema,isRawShape=input!==void 0&&input!==null&&typeof input==="object"&&typeof input.safeParse!=="function",annotations=config.annotations??TOOL_ANNOTATIONS[name]??FACADE_TOOL_ANNOTATIONS[name],finalConfig={...config,...isRawShape?{inputSchema:strictObject(input)}:{},...annotations?{annotations}:{}};return original(name,finalConfig,cb)};mcp.registerTool=patched}import{readdir as readdir2,stat}from"fs/promises";import{isAbsolute as isAbsolute3,join as join28}from"path";var MAX_BYTES=5242880;function registerAssetTools(mcp,ctx){mcp.registerTool("list_assets",{description:"List the folder's assets/ files \u2014 the `/assets/<name>` URL for <Image src>, size, whether any screen or snippet still references it, and the originating prompt for anything `generate_asset` produced. Check here before spending on art you may already have.",inputSchema:{unusedOnly:boolean().optional().describe("Only assets nothing references")}},async({unusedOnly})=>{let dir=join28(ctx.folder.root,"assets"),names=await readdir2(dir).catch(()=>[]),provenance=await readAssetsFile(ctx.folder.root),assets=[];for(let name of names.sort()){if(name.startsWith("."))continue;let info=await stat(join28(dir,name)).catch(()=>null);if(!info?.isFile())continue;let usedBy=assetReferences(ctx.folder,`assets/${name}`);if(unusedOnly&&usedBy.length>0)continue;let gen=provenance.generated[`assets/${name}`];assets.push({url:`/assets/${name}`,bytes:info.size,usedBy,...gen?{generated:{prompt:gen.prompt,intent:gen.intent,generatedAt:gen.generatedAt}}:{}})}return jsonResult({assets})}),mcp.registerTool("upload_asset",{description:"Write base64 data into assets/ and get the `/assets/<name>` URL for <Image src>. Author SVG art directly and upload it \u2014 never fall back to stock placeholders for intentional imagery. Max 5MB. For files already on disk, prefer `import_assets`.",inputSchema:{filename:string().describe('e.g. "hero-grain.svg", "cover.png"'),data:string().describe("base64-encoded file contents"),overwrite:boolean().optional().describe("Default true")}},async({filename,data,overwrite})=>{let safe=sanitizeFilename(filename);if(!isAllowedAssetExt(safe))return errorResult(`upload_asset: ${safe} is not an allowed asset type (${[...ALLOWED_ASSET_EXTENSIONS].join(", ")})`);let bytes;try{bytes=Buffer.from(data,"base64")}catch{return errorResult("upload_asset: data is not valid base64")}if(bytes.length===0||bytes.length>MAX_BYTES)return errorResult(`upload_asset: decoded size ${bytes.length} bytes is outside (0, ${MAX_BYTES}]`);let abs=join28(ctx.folder.root,"assets",safe);if(overwrite===!1&&await Bun.file(abs).exists())return errorResult(`upload_asset: ${safe} exists (overwrite: false)`);let result=await storeAsset(ctx.folder.root,safe,bytes);return jsonResult(result)}),mcp.registerTool("import_assets",{description:"Bulk-import image/SVG files into assets/ BY PATH \u2014 no base64 \u2014 returning the `/assets/<name>` URL per file. Globs are expanded relative to `baseDir` (default: the server's working dir), so `paths: ['../explore/*.png']` pulls in a whole batch. Max 5MB each; bad or missing files are reported per entry rather than failing the call.",inputSchema:{paths:array(string()).min(1).describe("Absolute or relative file paths and/or globs, e.g. ['../gen/*.png', '/abs/logo.svg']"),baseDir:string().optional().describe("Base directory for resolving relative paths and globs. Default: process cwd."),overwrite:boolean().optional().describe("Default true")}},async({paths,baseDir,overwrite})=>{let base=baseDir??process.cwd(),dir=join28(ctx.folder.root,"assets"),sources=[];for(let p of paths)if(p.includes("*")||p.includes("?")||p.includes("["))try{for await(let f of new Bun.Glob(p).scan({cwd:base,absolute:!0}))sources.push(f)}catch{}else sources.push(isAbsolute3(p)?p:join28(base,p));let results=[];for(let src of sources)try{let file=Bun.file(src);if(!await file.exists()){results.push({path:src,error:"not found"});continue}if(!isAllowedAssetExt(src)){results.push({path:src,error:"not an allowed image/font asset type"});continue}let size=file.size;if(size===0||size>MAX_BYTES){results.push({path:src,error:`size ${size} bytes outside (0, ${MAX_BYTES}]`});continue}let safe=sanitizeFilename(src);if(overwrite===!1&&await Bun.file(join28(dir,safe)).exists()){results.push({path:src,error:"exists (overwrite: false)"});continue}let stored=await storeAsset(ctx.folder.root,safe,Buffer.from(await file.arrayBuffer()));results.push({path:src,...stored})}catch(e){results.push({path:src,error:String(e.message??e)})}let imported=results.filter((r)=>("url"in r)).length;return jsonResult({imported,matched:sources.length,results})})}import{rm as rm5}from"fs/promises";import{join as join29}from"path";var asIs=(args)=>({ok:!0,args});function batchTool(name,body,normalize,run){return{body,prepare(rawArgs){let parsed=body.safeParse(rawArgs);if(!parsed.success)return{ok:!1,error:badRequest(`batch ${name}: invalid or missing args \u2014 match the standalone ${name} tool's schema.`,parsed.error.issues)};let normalized=normalize(parsed.data);if(!normalized.ok)return{ok:!1,error:badRequest(normalized.message,normalized.issues)};let args=normalized.args;return{ok:!0,run:(ctx)=>run(ctx,args)}}}}var BATCH_TOOLS={add_screen:batchTool("add_screen",AddScreenBody,asIs,addScreen2),remove_screen:batchTool("remove_screen",RemoveScreenBody,asIs,removeScreen2),set_screen_tree:batchTool("set_screen_tree",SetScreenTreeBody,asIs,setScreenTree2),add_board:batchTool("add_board",AddBoardBody,asIs,addBoard2),add_frame:batchTool("add_frame",AddFrameBody,asIs,addFrame2),remove_frame:batchTool("remove_frame",RemoveFrameBody,asIs,removeFrame2),add_node:batchTool("add_node",AddNodeBody,normalizeAddNode,addNode2),update_props:batchTool("update_props",UpdatePropsBody,asIs,updateProps2),remove_node:batchTool("remove_node",RemoveNodeBody,asIs,removeNode2),move_node:batchTool("move_node",MoveNodeBody,asIs,moveNode2),set_node_id:batchTool("set_node_id",SetNodeIdBody,asIs,setNodeId2),add_snippet:batchTool("add_snippet",AddSnippetBody,asIs,addSnippet2),update_snippet:batchTool("update_snippet",UpdateSnippetBody,asIs,updateSnippet2),remove_snippet:batchTool("remove_snippet",RemoveSnippetBody,asIs,removeSnippet2),instantiate_snippet:batchTool("instantiate_snippet",InstantiateSnippetBody,asIs,instantiateSnippet2),update_snippet_instance:batchTool("update_snippet_instance",UpdateSnippetInstanceBody,normalizeUpdateSnippetInstance,updateSnippetInstance),update_frame:batchTool("update_frame",UpdateFrameBody,asIs,updateFrames2),add_note:batchTool("add_note",AddNoteBody,asIs,addNote2)};function resourceFile(folder,kind,id){switch(kind){case"screen":return join29(folder.root,"screens",`${id}.json`);case"board":return join29(folder.root,"boards",`${id}.json`);case"snippet":return join29(folder.root,"snippets",`${id}.json`);case"notes":return join29(folder.root,"boards",`${id}.notes.json`);case"annotations":return join29(folder.root,"screens",`${id}.annotations.json`)}}function readResource(folder,kind,id){switch(kind){case"screen":return folder.screens.get(id);case"board":return folder.boards.get(id);case"snippet":return folder.snippets.get(id);case"notes":return folder.notes.get(id);case"annotations":return folder.annotations.get(id)}}function writeResourceMemory(folder,kind,id,value){switch(kind){case"screen":folder.screens.set(id,value);return;case"board":folder.boards.set(id,value);return;case"snippet":folder.snippets.set(id,value);return;case"notes":folder.notes.set(id,value);return;case"annotations":folder.annotations.set(id,value);return}}function deleteResourceMemory(folder,kind,id){switch(kind){case"screen":folder.screens.delete(id),folder.annotations.delete(id);return;case"board":folder.boards.delete(id),folder.notes.delete(id);return;case"snippet":folder.snippets.delete(id);return;case"notes":folder.notes.delete(id);return;case"annotations":folder.annotations.delete(id);return}}function touchedResources(ctx,call){let a=call.args;switch(call.tool){case"add_node":case"update_props":case"remove_node":case"move_node":case"set_node_id":case"set_screen_tree":case"update_snippet_instance":case"instantiate_snippet":{let screenId=a.screenId;if(isSnippetTreeId(screenId))return[{kind:"snippet",id:snippetIdFromTreeId(screenId)}];return[{kind:"screen",id:screenId}]}case"remove_screen":{let screenId=a.screenId,touched=[{kind:"screen",id:screenId},{kind:"annotations",id:screenId}];for(let[boardId,board]of ctx.folder.boards)if(board.frames.some((f)=>f.screen===screenId))touched.push({kind:"board",id:boardId});return touched}case"update_snippet":case"remove_snippet":return[{kind:"snippet",id:a.snippetId}];case"add_frame":case"update_frame":case"remove_frame":return[{kind:"board",id:a.boardId}];case"add_note":return[{kind:"notes",id:a.boardId}];default:return[]}}function createdResource(call,value){let v=value;switch(call.tool){case"add_screen":return{kind:"screen",id:v.screenId};case"add_board":return{kind:"board",id:v.boardId};case"add_snippet":return{kind:"snippet",id:v.snippetId};default:return null}}async function runBatch(ctx,calls,opts={}){let atomic=opts.atomic??!0,snapshots=new Map,created=[],buffered=[],undoDepth=ctx.folder.history.depths().undo,stagedCtx=atomic?{...ctx,broadcast:(e)=>buffered.push(e)}:ctx;function snapshot(kind,id){let key=`${kind}:${id}`;if(snapshots.has(key))return;let value=readResource(ctx.folder,kind,id);snapshots.set(key,{kind,id,existed:value!==void 0,value:value===void 0?void 0:structuredClone(value)})}function withResourceLock(kind,id,fn){switch(kind){case"board":case"notes":return withBoardLock(ctx.folder,id,fn);case"snippet":return withSnippetLock(ctx.folder,id,fn);default:return withScreenLock(ctx.folder,id,fn)}}async function rollback(){for(let res of created){if(snapshots.has(`${res.kind}:${res.id}`))continue;await withResourceLock(res.kind,res.id,async()=>{deleteResourceMemory(ctx.folder,res.kind,res.id),await rm5(resourceFile(ctx.folder,res.kind,res.id),{force:!0})})}for(let snap of snapshots.values())await withResourceLock(snap.kind,snap.id,async()=>{if(!snap.existed){deleteResourceMemory(ctx.folder,snap.kind,snap.id),await rm5(resourceFile(ctx.folder,snap.kind,snap.id),{force:!0});return}if(writeResourceMemory(ctx.folder,snap.kind,snap.id,structuredClone(snap.value)),(snap.kind==="notes"||snap.kind==="annotations")&&Array.isArray(snap.value)&&snap.value.length===0)await rm5(resourceFile(ctx.folder,snap.kind,snap.id),{force:!0});else await writeJsonAtomic(resourceFile(ctx.folder,snap.kind,snap.id),snap.value)});ctx.folder.history.truncateUndoTo(undoDepth)}let isIndexPath=(p)=>Array.isArray(p)||typeof p==="string"&&p.trim().startsWith("["),indexEdits=new Map;for(let call of calls){if(call.tool!=="remove_node"&&call.tool!=="move_node")continue;let a=call.args,p=a.path??a.fromPath;if(typeof a.screenId==="string"&&isIndexPath(p))indexEdits.set(a.screenId,(indexEdits.get(a.screenId)??0)+1)}let footgun=[...indexEdits].find(([,n])=>n>=2);if(footgun)return{completed:0,total:calls.length,rolledBack:atomic,results:[{tool:"batch",ok:!1,error:badRequest(`batch: ${footgun[1]} remove_node/move_node calls on screen "${footgun[0]}" use numeric paths. Indices shift as earlier siblings are removed, so later paths miss and the batch rolls back. Give each target a stable @id (set_node_id) and address it as "@id", or order removals deepest-index-first.`)}]};let results=[];for(let call of calls){let tool=BATCH_TOOLS[call.tool];if(!tool){results.push({tool:call.tool,ok:!1,error:badRequest(`batch: unsupported tool "${call.tool}"`)});break}let prepared=tool.prepare(call.args);if(!prepared.ok){results.push({tool:call.tool,ok:!1,error:prepared.error});break}if(atomic)for(let res of touchedResources(ctx,call))snapshot(res.kind,res.id);let r=await prepared.run(stagedCtx);if(r.ok){let made=createdResource(call,r.value);if(made)created.push(made);results.push({tool:call.tool,ok:!0,value:r.value})}else{results.push({tool:call.tool,ok:!1,error:r.error});break}}let failed=results.some((r)=>!r.ok);if(failed&&atomic)await rollback();else if(atomic)for(let e of buffered)ctx.broadcast(e);return{completed:results.filter((r)=>r.ok).length,total:calls.length,rolledBack:failed&&atomic,results}}var RAW_PALETTE_FAMILIES=new Set(["slate","gray","zinc","neutral","stone","red","orange","amber","yellow","lime","green","emerald","teal","cyan","sky","blue","indigo","violet","purple","fuchsia","pink","rose"]),SEMANTIC_NAMES=new Set(["background","foreground","card","card-foreground","popover","popover-foreground","primary","primary-foreground","secondary","secondary-foreground","muted","muted-foreground","accent","accent-foreground","destructive","destructive-foreground","border","input","ring"]),COLOR_NEUTRAL_LITERALS=new Set(["transparent","current","inherit"]),FIXED_COLOR_LITERALS=new Set(["white","black"]),COLOR_CAPABLE_PREFIXES=["bg","text","border","ring","outline","divide","from","via","to","fill","stroke","shadow","decoration","accent","caret","placeholder"],VARIANT_RE=/^([^:]+:)+/,OPACITY_SUFFIX_RE=/\/[0-9]+$/;function stripDecorations(cls){let m=cls.match(VARIANT_RE),variantPrefix=m?m[0]:"";return{core:cls.slice(variantPrefix.length).replace(OPACITY_SUFFIX_RE,""),variantPrefix}}function looksLikeArbitraryColor(value){return/^#[0-9a-fA-F]{3,8}$/.test(value)||/^(rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|color)\(/i.test(value)}function parseColorClass(cls){let{core,variantPrefix}=stripDecorations(cls);for(let prefix of COLOR_CAPABLE_PREFIXES){let head=`${prefix}-`;if(!core.startsWith(head))continue;let rest=core.slice(head.length);if(rest.length===0)continue;if(rest.startsWith("[")&&rest.endsWith("]")){let inner=rest.slice(1,-1);if(!looksLikeArbitraryColor(inner))return null;return{raw:cls,variantPrefix,prefix,family:"arbitrary",isPalette:!1,isSemantic:!1,isLiteral:!1,isArbitraryColor:!0}}let firstSeg=rest.split("-")[0]??rest;if(COLOR_NEUTRAL_LITERALS.has(rest)||COLOR_NEUTRAL_LITERALS.has(firstSeg))return null;if(FIXED_COLOR_LITERALS.has(rest)||FIXED_COLOR_LITERALS.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!1,isSemantic:!1,isLiteral:!0,isArbitraryColor:!1};if(RAW_PALETTE_FAMILIES.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!0,isSemantic:!1,isLiteral:!1,isArbitraryColor:!1};if(SEMANTIC_NAMES.has(rest))return{raw:cls,variantPrefix,prefix,family:rest,isPalette:!1,isSemantic:!0,isLiteral:!1,isArbitraryColor:!1};if(SEMANTIC_NAMES.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!1,isSemantic:!0,isLiteral:!1,isArbitraryColor:!1};return null}return null}function suggestSemanticFor(parsed){if(!parsed.isPalette&&!parsed.isLiteral)return null;let{variantPrefix,prefix,family,raw}=parsed;if(prefix==="bg"){if(family==="white"||parsed.isPalette&&raw.endsWith("-50"))return`${variantPrefix}bg-card`;if(family==="black"||parsed.isPalette&&/-95\d?$/.test(raw))return`${variantPrefix}bg-background`;if(parsed.isPalette&&/-(900|800|700)$/.test(raw))return`${variantPrefix}bg-muted`}if(prefix==="text"){if(family==="black"||parsed.isPalette&&/-(900|800)$/.test(raw))return`${variantPrefix}text-foreground`;if(family==="white"||parsed.isPalette&&/-(50|100)$/.test(raw))return`${variantPrefix}text-foreground`;if(parsed.isPalette&&/-(400|500|600)$/.test(raw))return`${variantPrefix}text-muted-foreground`}if((prefix==="border"||prefix==="divide")&&parsed.isPalette)return`${variantPrefix}${prefix}-border`;if(prefix==="ring"&&parsed.isPalette)return`${variantPrefix}ring-ring`;return null}function auditClasses(className){let tokens=className.split(/\s+/).filter(Boolean),raw=[],suggestions={},hasColor=!1,allSemantic=!0;for(let token of tokens){let parsed=parseColorClass(token);if(!parsed)continue;if(hasColor=!0,parsed.isSemantic)continue;raw.push(token),allSemantic=!1;let suggestion=suggestSemanticFor(parsed);if(suggestion)suggestions[token]=suggestion}return{raw,suggestions,hasColor,allSemantic}}function darkModeAuditTree(root){let problems=[],totalColored=0,semanticCount=0;function walk(node,path){if(isComponentNode(node)){let accentProp=node.props?.["data-accent"];if(!(accentProp!==void 0&&accentProp!==null&&accentProp!==!1&&accentProp!=="")){let className=typeof node.props?.className==="string"?node.props.className:"";if(className){let{raw,suggestions,hasColor,allSemantic}=auditClasses(className);if(hasColor){if(totalColored++,allSemantic)semanticCount++;if(raw.length>0)problems.push({path:[...path],ref:node.$ref,className,raw,suggestions})}}}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}}else if(isSnippetInstance(node));}return walk(root,[]),{coverage:totalColored===0?1:semanticCount/totalColored,problems,totalColored}}import{__unstable__loadDesignSystem}from"@tailwindcss/node";var designSystemPromise=null,designSystemKey=null;function getDesignSystem(jit){return jit.entryCss().then(({css,base})=>{if(designSystemPromise&&designSystemKey===css)return designSystemPromise;return designSystemKey=css,designSystemPromise=__unstable__loadDesignSystem(css,{base}),designSystemPromise})}function definedInCustomCss(customCss,cls){if(!customCss||!/^[a-z][a-z0-9:_-]*$/i.test(cls))return!1;let escaped=cls.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\.${escaped}(?![\\w-])`).test(customCss)}function referencedVars(css){let out=new Set,re=/var\(\s*(--[a-zA-Z0-9-]+)/g,m=re.exec(css);while(m!==null)out.add(m[1]),m=re.exec(css);return[...out]}function declaredVars(css){let out=new Set,decl=/(--[a-zA-Z0-9-]+)\s*:/g,m=decl.exec(css);while(m!==null)out.add(m[1]),m=decl.exec(css);let prop=/@property\s+(--[a-zA-Z0-9-]+)/g;m=prop.exec(css);while(m!==null)out.add(m[1]),m=prop.exec(css);return out}function unresolvedVars(css,theme){let declared=declaredVars(css),out=[];for(let name of referencedVars(css)){if(declared.has(name))continue;if(name.startsWith("--tw-"))continue;if(theme.get([name])===null)out.push(name)}return out}async function validateClassNames(jit,customCss,classes){let ds=await getDesignSystem(jit);return classes.map((cls)=>{let trimmed=cls.trim();if(trimmed==="")return{class:cls,valid:!1,reason:"empty class"};try{let parsed=ds.parseCandidate(trimmed);if(parsed.length>0){let generated=ds.candidatesToCss([trimmed])[0];if(generated!==null&&generated!==void 0){let dangling=unresolvedVars(generated,ds.theme);if(dangling.length>0)return{class:cls,valid:!0,warning:`resolves to undefined CSS var ${dangling.join(", ")}; will fall back at runtime`};return{class:cls,valid:!0}}}if(definedInCustomCss(customCss,trimmed))return{class:cls,valid:!0,reason:"defined in custom_css (not a Tailwind utility)"};return{class:cls,valid:!1,reason:parsed.length===0?"no matching Tailwind utility":"candidate produced no CSS"}}catch(err){if(definedInCustomCss(customCss,trimmed))return{class:cls,valid:!0,reason:"defined in custom_css (not a Tailwind utility)"};return{class:cls,valid:!1,reason:err instanceof Error?err.message:String(err)}}})}function classUses(root,prefix){let out=[],walk=(node,path)=>{if(!isComponentNode(node))return;let className=typeof node.props?.className==="string"?node.props.className.trim():"";if(className)out.push({path,className,classes:className.split(/\s+/).filter(Boolean)});for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}};return walk(root,prefix),out}var RAW_COLOR_LIMIT=8;async function diagnosticsForTree(ctx,jit,thing,root,prefix=[]){if(!jit)return[];if(!styleChannelOf(providerForScreen2(ctx,thing),ctx.folder.config.styling?.framework).needsTailwindJit)return[];let uses=classUses(root,prefix),unique=[...new Set(uses.flatMap((use)=>use.classes))],reports=unique.length?await validateClassNames(jit,ctx.folder.customCss,unique):[],reportByClass=new Map(reports.map((report)=>[report.class,report])),diagnostics=[];for(let use of uses)for(let cls of use.classes){let report=reportByClass.get(cls);if(!report)continue;if(!report.valid)diagnostics.push({severity:"warning",code:"tailwind/invalid-class",path:use.path,message:`\`${cls}\` does not compile: ${report.reason??"no matching utility"}`});else if(report.warning)diagnostics.push({severity:"warning",code:"tailwind/undefined-var",path:use.path,message:`\`${cls}\` ${report.warning}`})}let hostRoot=hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp);if(detectTailwindMajor(hostRoot)===3){let issues=new Map(v3ClassIssues(unique).map((issue)=>[issue.class,issue]));for(let use of uses)for(let cls of use.classes){let issue=issues.get(cls);if(!issue)continue;diagnostics.push({severity:"warning",code:"tailwind/v3",path:use.path,message:`\`${cls}\` uses Tailwind v4 semantics in a v3 host: ${issue.note}`,...issue.v3?{suggestion:issue.v3}:{}})}}return diagnostics.push(...rawColorDiagnostics(root,prefix)),diagnostics}function rawColorDiagnostics(root,prefix=[]){let problems=darkModeAuditTree(root).problems,out=problems.slice(0,RAW_COLOR_LIMIT).map((problem)=>({severity:"warning",code:"theme/raw-color",path:[...prefix,...problem.path],message:`${problem.ref} uses non-flipping color classes: ${problem.raw.join(", ")}`,...Object.keys(problem.suggestions).length?{suggestion:Object.entries(problem.suggestions).map(([from,to])=>`${from} \u2192 ${to}`).join(", ")}:{}}));if(problems.length>RAW_COLOR_LIMIT)out.push({severity:"warning",code:"theme/raw-color",path:[...prefix],message:`${problems.length-RAW_COLOR_LIMIT} more node(s) use non-flipping color classes. `+"If these are deliberate \u2014 text over an image or scrim, a brand accent, chrome tuned for one mode \u2014 "+'set `data-accent: "ok"` on each to exempt it, rather than taking a suggestion that would break the design. The opt-out does not cascade to children.'});return out}function pathsUsing(root,ref){let out=[],walk=(node,path)=>{if(!isComponentNode(node))return;if(node.$ref===ref)out.push(path);for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}};return walk(root,[]),out}function renderDiagnostics(ctx,screen){let failures;try{failures=renderBodyGuarded(screen,registryForScreen2(ctx,screen),ctx.folder.snippets).failures}catch{return[]}return failures.flatMap((failure)=>{let paths=pathsUsing(screen.tree,failure.componentId);return(paths.length>0?paths:[[]]).map((path)=>({severity:"error",code:"render/component-threw",path,message:`\`${failure.componentId}\` failed to render and was replaced with a placeholder: ${failure.reason}`}))})}async function diagnosticsForScreen(ctx,jit,screen){return[...renderDiagnostics(ctx,screen),...await diagnosticsForTree(ctx,jit,screen,screen.tree)]}var hiddenTreeTools=new Set(["add_node","set_screen_tree","instantiate_snippet"]),batchableTools=Object.keys(BATCH_TOOLS).filter((name)=>!hiddenTreeTools.has(name));function registerBatchTool(mcp,ctx,jit){mcp.registerTool("batch",{description:"Run a sequence of mutation calls in one round-trip; each entry is `{ tool, args }` with that tool's own arguments. Atomic by default \u2014 the first error rolls every touched resource back to its pre-batch state and reports `rolledBack: true` with the failing call. `atomic: false` runs until error and keeps completed work.",annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1},inputSchema:{calls:array(strictObject({tool:_enum(batchableTools),args:record(string(),unknown())})).min(1).max(100),atomic:boolean().optional().describe("Default true")}},async({calls,atomic})=>{let{value:result,ops}=await collectActivity(()=>runBatch(ctx,calls,{atomic}));if(!result.rolledBack)emitGroupedActivity(ctx,"batch",ops);let failed=result.results.some((r)=>!r.ok),diagnostics={};if(!result.rolledBack&&jit){let screenIds=new Set,snippetIds=new Set;for(let call of calls){if(typeof call.args.screenId==="string")screenIds.add(call.args.screenId);if(typeof call.args.snippetId==="string")snippetIds.add(call.args.snippetId);if(call.tool==="add_screen"&&typeof call.args.id==="string")screenIds.add(call.args.id);if(call.tool==="add_snippet"&&typeof call.args.id==="string")snippetIds.add(call.args.id)}for(let screenId of screenIds){let screen=ctx.folder.screens.get(screenId);if(!screen)continue;let found=await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]);if(found.length>0)diagnostics[`screen:${screenId}`]=found}for(let snippetId of snippetIds){let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)continue;let found=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);if(found.length>0)diagnostics[`snippet:${snippetId}`]=found}}return{...failed?{isError:!0}:{},content:[{type:"text",text:JSON.stringify({...result,...Object.keys(diagnostics).length>0?{diagnostics}:{}})}]}})}import{readFileSync as readFileSync6}from"fs";import{join as join30}from"path";var sessions=new Map;function activeSessions(){return[...sessions.values()].filter((s)=>s.endedAt===null)}function sessionStatuses(list=sessions.values()){return[...list].map((session)=>{let open=session.endedAt===null,read=(get)=>{try{return get()}catch{return null}};return{sessionId:session.handle.sessionId,status:open?"open":"closed",startedUrl:session.url??null,currentUrl:open?read(()=>session.handle.currentUrl()):null,openedAt:new Date(session.startedAt).toISOString(),endedAt:session.endedAt===null?null:new Date(session.endedAt).toISOString(),captured:(read(()=>session.handle.captures())??[]).map((m)=>({captureId:m.id,url:m.finalUrl||m.url}))}})}async function openSession(ctx,opts){let existing=activeSessions()[0];if(existing)throw Error(`a capture session is already open (${existing.handle.sessionId})${existing.url?` on ${existing.url}`:""} \u2014 finish it in the browser window before starting another`);let folderId=ctx.folder.config.folderId,statePath=opts.url?sessionStatePath(ctx.folder.root,new URL(opts.url).origin,folderId):void 0,handle=await startCaptureSession({folderRoot:ctx.folder.root,...folderId?{folderId}:{},...opts.url?{url:opts.url}:{},...statePath?{sessionStatePath:statePath}:{},...opts.onCapture?{onCapture:opts.onCapture}:{},...opts.onStatus?{onStatus:opts.onStatus}:{}}),session={handle,url:opts.url,startedAt:Date.now(),endedAt:null};return sessions.set(handle.sessionId,session),handle.finished.then(()=>{session.endedAt=Date.now()}),session}function jsonResult2(value){return{content:[{type:"text",text:JSON.stringify(value)}]}}function readBytes(path,length=32){try{return readFileSync6(path).subarray(0,length)}catch{return Buffer.alloc(0)}}function invisibleNodes(dom,verticalScope){let hidden=new Set,offscreen=new Set;for(let n of dom.nodes){if(n.parent!==null&&hidden.has(n.parent))hidden.add(n.i);else if(n.style.opacity!==void 0&&Number(n.style.opacity)===0)hidden.add(n.i);let{x,y,w,h}=n.rect;if(x+w<=0||y+h<=(verticalScope?.from??0)||x>=dom.viewport.w||verticalScope!==void 0&&y>=verticalScope.to)offscreen.add(n.i)}return{hidden,offscreen}}function meaningfulNodes(dom){return dom.nodes.filter((n)=>n.repeat!==void 0||n.text!==void 0&&n.text.length>1||["header","nav","main","section","footer","aside","form","table"].includes(n.tag))}function spatialSample(nodes,dom,limit){if(nodes.length<=limit)return nodes;let selected=new Set,viewportHeight=Math.max(1,dom.viewport.h),bandCount=Math.max(1,Math.ceil(dom.documentHeight/viewportHeight)),bandBudget=Math.min(bandCount,Math.max(1,Math.floor(limit/2)));for(let slot=0;slot<bandBudget;slot++){let band=Math.min(bandCount-1,Math.floor(slot*bandCount/bandBudget)),inBand=nodes.filter((n)=>Math.floor(Math.max(0,n.rect.y)/viewportHeight)===band),pick=inBand.find((n)=>n.repeat!==void 0)??inBand.find((n)=>["header","nav","main","section","footer","aside"].includes(n.tag))??inBand[0];if(pick)selected.add(pick.i)}let remaining=limit-selected.size;for(let slot=0;slot<remaining;slot++){let index=Math.min(nodes.length-1,Math.floor(slot*nodes.length/remaining)),pick=nodes[index];if(pick)selected.add(pick.i)}if(selected.size<limit){for(let node of nodes)if(selected.add(node.i),selected.size>=limit)break}return nodes.filter((n)=>selected.has(n.i)).slice(0,limit)}function outlineOf(dom,limit=60,verticalScope){let{hidden,offscreen}=invisibleNodes(dom,verticalScope),lines=[],sampled=spatialSample(meaningfulNodes(dom),dom,limit);for(let n of sampled){let indent=" ".repeat(Math.min(n.depth,8)),rep=n.repeat?` \xD7${n.repeat.count}`:"",text=n.text?` "${n.text.slice(0,60)}"`:"",flags=[];if(hidden.has(n.i))flags.push(Number(n.style.opacity)===0?"HIDDEN(opacity:0)":"HIDDEN");if(offscreen.has(n.i))flags.push("OFFSCREEN");let marks=flags.length?` ${flags.join(" ")}`:"",{x,y,w,h}=n.rect;lines.push(`${indent}${n.tag}${rep}${text} [${w}\xD7${h} @${x},${y}]${marks}`)}let ys=sampled.flatMap((n)=>[n.rect.y,n.rect.y+n.rect.h]);return{lines,shown:lines.length,hiddenCount:hidden.size+offscreen.size,coverage:{fromY:ys.length?Math.min(...ys):0,toY:ys.length?Math.max(...ys):0,documentHeight:dom.documentHeight}}}function queriedNodes(dom,input){let{hidden,offscreen}=invisibleNodes(dom,input.verticalScope),tags=input.tags?new Set(input.tags.map((tag)=>tag.toLowerCase())):null,matches=dom.nodes.filter((node)=>{if(tags&&!tags.has(node.tag))return!1;if(input.visibleOnly&&(hidden.has(node.i)||offscreen.has(node.i)))return!1;if(input.yFrom!==void 0&&node.rect.y+node.rect.h<=input.yFrom)return!1;if(input.yTo!==void 0&&node.rect.y>=input.yTo)return!1;return!0}),offset=input.offset??0,limit=input.limit??200,nodes=matches.slice(offset,offset+limit),next=offset+nodes.length;return{nodes,total:matches.length,offset,nextOffset:next<matches.length?next:null}}function previewOf(png,manifest,input){let bitmap=pngSize2(png);if(!bitmap)return{error:"capture preview is not a readable PNG"};let viewportWidth=manifest.geometry?.viewportCss.width??manifest.viewport.w,viewportHeight=manifest.geometry?.viewportCss.height??manifest.viewport.h,bitmapScale=viewportWidth>0?bitmap.width/viewportWidth:1,out=png,cssRegion=null;if(input.mode!=="overview"){let scroll=manifest.geometry?.scrollCss??{x:0,y:0},sourceIsViewport=manifest.geometry?.screenshot?.mode==="viewport",x=input.mode==="viewport"?scroll.x:input.xFrom??0,y=input.mode==="viewport"?scroll.y:input.yFrom??0,xTo=input.mode==="viewport"?x+viewportWidth:input.xTo??viewportWidth,yTo=input.mode==="viewport"?y+viewportHeight:input.yTo??y+viewportHeight;if(xTo<=x||yTo<=y)return{error:"capture preview crop must have positive width and height"};if(cssRegion={x,y,w:xTo-x,h:yTo-y},!(input.mode==="viewport"&&sourceIsViewport)){let origin=sourceIsViewport?scroll:{x:0,y:0},pixelRegion={x:Math.round((x-origin.x)*bitmapScale),y:Math.round((y-origin.y)*bitmapScale),w:Math.round((xTo-x)*bitmapScale),h:Math.round((yTo-y)*bitmapScale)};if(pixelRegion.x>=bitmap.width||pixelRegion.y>=bitmap.height||pixelRegion.x+pixelRegion.w<=0||pixelRegion.y+pixelRegion.h<=0)return{error:"capture preview crop does not intersect page.png"};let left=Math.max(0,pixelRegion.x),top=Math.max(0,pixelRegion.y),right=Math.min(bitmap.width,pixelRegion.x+pixelRegion.w),bottom=Math.min(bitmap.height,pixelRegion.y+pixelRegion.h);out=cropPng(png,{x:left,y:top,w:right-left,h:bottom-top},0),cssRegion={x:origin.x+left/bitmapScale,y:origin.y+top/bitmapScale,w:(right-left)/bitmapScale,h:(bottom-top)/bitmapScale}}}let size=pngSize2(out);if(!size)return{error:"capture preview crop is not a readable PNG"};let maxWidth=input.maxWidth??1400,maxPixels=4000000,factor=Math.max(1,Math.ceil(size.width/maxWidth),Math.ceil(Math.sqrt(size.width*size.height/maxPixels)));if(factor>1)out=downscalePng(out,factor);while(out.byteLength>3500000){let current=pngSize2(out);if(!current||current.width===1&¤t.height===1)break;out=downscalePng(out,2),factor*=2}let finalSize=pngSize2(out);return{png:out,meta:{mode:input.mode,bitmap:finalSize,sourceBitmap:bitmap,bitmapScale,downscaleFactor:factor,...cssRegion?{cssRegion}:{}}}}function captureCapabilities(manifest){if(manifest.themeOnly)return["theme-import"];if(manifest.stability?.status==="unstable")return["preview"];return["theme-import","outline","comparison","preview"]}function registerCaptureTools(mcp,ctx){let folderId=()=>ctx.folder.config.folderId;mcp.registerTool("start_capture_session",{description:"Open a real browser window the USER drives, to reach a page behind a login or on staging. Returns a sessionId IMMEDIATELY \u2014 never treat it as blocking and never re-call it to check; poll list_captures instead, and tell the user to log in, hit **Capture page** per page, then **Done**. Guide: velloo://guide/capture.",inputSchema:{url:string().optional().describe("Page to open the browser on. The session is scoped to this origin.")}},async({url})=>{if(url!==void 0){let scheme;try{scheme=new URL(url).protocol}catch{return errorResult(`Invalid url: ${url}`)}if(scheme!=="http:"&&scheme!=="https:")return errorResult(`start_capture_session: only http(s) URLs are allowed (got ${scheme})`)}try{let session=await openSession(ctx,{...url?{url}:{},onStatus:(message)=>{console.error(`velloo capture: ${message}`)}});return emitActivity(ctx,"start_capture_session",{}),console.error(`velloo capture: an agent opened a capture session${url?` on ${url}`:""} \u2014 the browser window is yours to drive.`),jsonResult2({sessionId:session.handle.sessionId,url:url??null,status:"open",note:"The browser window is open and the user is driving it. This call did NOT wait \u2014 poll list_captures, whose `sessions[]` reports whether the window is still open and what page the user is on, and tell them to hit 'Capture page' in the toolbar on each page you need, then 'Done'."})}catch(err){if(err instanceof HeadedBrowserMissingError)return errorResult(err.message);return errorResult(`start_capture_session failed: ${err instanceof Error?err.message:String(err)}`)}}),mcp.registerTool("list_captures",{description:"Stored browser captures for this folder, newest first, plus open session state. `kind`, `purpose`, and `usableFor` distinguish automatic theme snapshots from user-requested frozen pages; geometry names CSS, bitmap, viewport, DPR, and replay sizes separately. Read a capture with `get_capture`; compare a page capture via `compare_to_url { source: { captureId } }`. Poll `sessions[]` rather than re-calling `start_capture_session`.",inputSchema:{}},async()=>{let captures=listCaptures(ctx.folder.root,folderId()).map((m)=>({captureId:m.id,url:m.finalUrl||m.url,title:m.title,capturedAt:m.capturedAt,viewport:m.viewport,themeOnly:m.themeOnly,kind:m.kind??(m.themeOnly?"theme":"page"),purpose:m.themeOnly?"Automatic theme snapshot; use for import_theme, not visual comparison.":"User-requested frozen page; use for outline, preview, and comparison.",usableFor:captureCapabilities(m),...m.sessionId?{sessionId:m.sessionId}:{},...m.stability?{stability:m.stability.status}:{},...m.geometry?{geometry:m.geometry}:{},nodeCount:m.nodeCount,assetCount:m.assetCount})),sessions=sessionStatuses(),open=sessions.find((s)=>s.status==="open");return jsonResult2({captures,sessions,...open?{note:`A capture session is open${open.currentUrl?` on ${open.currentUrl}`:""}. The user drives it: ask them to reach each page you need and hit 'Capture page', then 'Done'. Poll this tool \u2014 do not call start_capture_session again.`}:captures.length===0?{note:sessions.length?"The capture session has closed and produced nothing. Ask the user whether they hit 'Capture page' before 'Done', then start another session.":"No captures yet. `start_capture_session` opens a browser for the user to log in and capture pages from."}:{}})}),mcp.registerTool("get_capture",{description:"Read one stored capture: a spatial page outline with rects, visibility markers, repeated blocks, theme CSS, assets, and explicit CSS/bitmap/replay geometry. Use `image` for a bounded inline overview, viewport, or crop of authoritative `page.png`; use `full: true` or y/tag filters with offset + limit for paginated computed-style nodes. Feed the theme CSS to `import_theme` BEFORE composing. Guide: velloo://guide/capture.",inputSchema:{captureId:string(),full:boolean().optional().describe("Return the first paginated node page with computed styles. Default false."),yFrom:number().nonnegative().optional().describe("Only nodes crossing this CSS-pixel Y."),yTo:number().positive().optional().describe("Only nodes before this CSS-pixel Y."),tags:array(string().min(1)).optional(),visibleOnly:boolean().optional(),offset:number().int().nonnegative().optional(),limit:number().int().min(1).max(500).optional().describe("Default 200, max 500."),image:strictObject({mode:_enum(["overview","viewport","crop"]),xFrom:number().nonnegative().optional(),xTo:number().positive().optional(),yFrom:number().nonnegative().optional(),yTo:number().positive().optional(),maxWidth:number().int().min(100).max(2000).optional()}).optional().describe("Return a bounded inline PNG overview, captured viewport, or CSS-coordinate crop.")}},async({captureId,full,yFrom,yTo,tags,visibleOnly,offset,limit,image})=>{if(!isSafeCaptureId(captureId))return errorResult(`Invalid captureId: ${captureId}`);let manifest=readCaptureManifest(ctx.folder.root,captureId,folderId());if(!manifest)return errorResult(`No capture "${captureId}" for this folder \u2014 call list_captures.`);let dir=captureDir(ctx.folder.root,captureId,folderId()),readJson=(file)=>{try{return JSON.parse(readFileSync6(join30(dir,file),"utf8"))}catch{return null}},theme=readJson("computed-vars.json"),dom=manifest.themeOnly?null:readJson("dom.json"),assets=readJson("assets.json"),verticalScope=manifest.geometry?.screenshot?.mode==="viewport"?{from:manifest.geometry.scrollCss.y,to:manifest.geometry.scrollCss.y+manifest.geometry.viewportCss.height}:void 0,outline=dom?outlineOf(dom,60,verticalScope):null,files=manifest.files.map((name)=>{let path=join30(dir,name),size=name.endsWith(".png")?pngSize2(readBytes(path)):null;return{name,path,...size??{}}}),screenshot=files.find((f)=>f.name==="page.png"),wantsNodes=Boolean(full||yFrom!==void 0||yTo!==void 0||tags||visibleOnly||offset||limit),nodePage=dom&&wantsNodes?queriedNodes(dom,{...yFrom!==void 0?{yFrom}:{},...yTo!==void 0?{yTo}:{},...tags!==void 0?{tags}:{},...visibleOnly!==void 0?{visibleOnly}:{},...offset!==void 0?{offset}:{},...limit!==void 0?{limit}:{},...verticalScope!==void 0?{verticalScope}:{}}):null,body={captureId,url:manifest.finalUrl||manifest.url,title:manifest.title,capturedAt:manifest.capturedAt,viewport:manifest.viewport,themeOnly:manifest.themeOnly,kind:manifest.kind??(manifest.themeOnly?"theme":"page"),usableFor:captureCapabilities(manifest),...manifest.stateId?{stateId:manifest.stateId}:{},...manifest.geometry?{geometry:manifest.geometry}:{},...manifest.stability?{stability:manifest.stability}:{},...manifest.stability?.status==="unstable"?{warning:"The page changed while screenshot and DOM evidence were recorded. Preview is available, but re-capture after the page settles before using its outline or comparison."}:{},...theme?{themeCss:theme.css,fonts:theme.fonts,tokenCount:Object.keys(theme.light).length+Object.keys(theme.dark).length}:{},...dom&&outline?nodePage?{documentHeight:dom.documentHeight,nodes:nodePage.nodes,nodeQuery:{total:nodePage.total,returned:nodePage.nodes.length,offset:nodePage.offset,nextOffset:nodePage.nextOffset},truncated:dom.truncated}:{documentHeight:dom.documentHeight,outline:outline.lines,outlineShown:outline.shown,outlineCoverage:outline.coverage,nodeCount:dom.nodes.length,truncated:dom.truncated,note:`Digest of ${outline.shown} of ${dom.nodes.length} nodes \u2014 rects are `+"`[w\xD7h @x,y]` in page coordinates. "+(outline.hiddenCount>0?`${outline.hiddenCount} node(s) are marked HIDDEN or OFFSCREEN: they lay out but do not appear in page.png, so a hover-only or off-stage block is NOT ordinary page content \u2014 model it as such or leave it out. `:"")+(screenshot?`page.png (${screenshot.width}\xD7${screenshot.height}) is the authoritative reference \u2014 open it. `:"")+"Use full: true or yFrom/yTo/tags with offset + limit for paginated computed styles."}:{},...assets?{assets:assets.assets}:{},dir,files,...screenshot?{verifyWith:`compare_to_url { screenId: "<your screen>", source: { captureId: "${captureId}" } }`}:{}};if(!image)return jsonResult2(body);if(!screenshot)return errorResult(`Capture "${captureId}" has no page.png to preview.`);let preview=previewOf(readFileSync6(screenshot.path),manifest,{mode:image.mode,...image.xFrom!==void 0?{xFrom:image.xFrom}:{},...image.xTo!==void 0?{xTo:image.xTo}:{},...image.yFrom!==void 0?{yFrom:image.yFrom}:{},...image.yTo!==void 0?{yTo:image.yTo}:{},...image.maxWidth!==void 0?{maxWidth:image.maxWidth}:{}});if("error"in preview)return errorResult(preview.error);return{content:[{type:"text",text:JSON.stringify({...body,preview:preview.meta})},{type:"image",data:preview.png.toString("base64"),mimeType:"image/png"}]}})}async function result(operation){try{return jsonResult(await operation())}catch(error){if(error instanceof CommentStoreError)return errorResult({kind:"CommentStore",code:error.code,message:error.message});throw error}}function registerCommentTools(mcp,comments){mcp.registerTool("list_comment_threads",{description:"List visual feedback threads. Every open thread is work waiting on you \u2014 there is no separate inbox. Returns all open threads across the folder by default; `boardId` narrows, `scope` separates local threads from cloud ones left on a published link. Anchors report their attached path, or stale when the original node is gone.",inputSchema:{boardId:string().optional(),status:_enum(["open","resolved","all"]).optional(),scope:_enum(["local","shared","all"]).optional()}},async({boardId,status="open",scope="all"})=>result(async()=>({threads:boardId?await comments.list(boardId,status,scope):await comments.listAll(status,scope)}))),mcp.registerTool("get_comment_thread",{description:"Read one complete visual feedback thread, including its anchor and replies.",inputSchema:{threadId:string().uuid()}},async({threadId})=>result(async()=>({thread:await comments.get(threadId)}))),mcp.registerTool("update_comment_thread",{description:'Act on a feedback thread: `reply` posts an agent message, `status` moves it to resolved / open / deleted. Both in one call is the normal close-out \u2014 reply saying what changed, then resolve. `status: "deleted"` is permanent; only on the user\'s explicit ask. Guide: velloo://guide/comments.',inputSchema:{threadId:string().uuid(),reply:string().trim().min(1).max(4000).optional().describe("Agent message to post"),status:_enum(["resolved","open","deleted"]).optional()}},async({threadId,reply,status})=>{if(reply===void 0&&status===void 0)return errorResult({kind:"BadRequest",message:"update_comment_thread: pass reply, status, or both."});return result(async()=>{let thread=reply?await comments.reply(threadId,{body:reply,author:{kind:"agent",displayName:"Agent"}}):void 0;if(status==="deleted")return comments.delete(threadId);if(status!==void 0)thread=await comments.setResolved(threadId,status==="resolved");return{thread:thread??await comments.get(threadId)}})})}var UNIVERSAL_PROPS=new Set(["className","children","id","style","title","role"]),manifestCache=new WeakMap;function manifestFor(provider){let cached=manifestCache.get(provider);if(!cached)cached=provider.loadManifest().catch(()=>[]),manifestCache.set(provider,cached);return cached}function isSubstitution(value){return typeof value==="object"&&value!==null&&(typeof value.$param==="string"||typeof value.$if==="string")}async function propWarnings(ctx,screen,ref,props){let provider=providerForScreen(screen,ctx.providers,ctx.defaultProvider),descriptor=(await manifestFor(provider)).find((c)=>c.id===ref),extension=ctx.folder.config.extensions?.[ref],known=descriptor?.props??extension?.props;if(!known||known.length===0)return[];let warnings=[],knownNames=known.map((p)=>p.name);for(let[key,value]of Object.entries(props)){if(UNIVERSAL_PROPS.has(key)||key.startsWith("data-")||key.startsWith("aria-"))continue;if(isSubstitution(value))continue;let prop=known.find((p)=>p.name===key);if(!prop){let near=nearestRefs(key,knownNames,3).join(", ");warnings.push(`${ref}: unknown prop "${key}"${near?` \u2014 closest known: ${near}`:""}`);continue}if(prop.control==="icon"&&prop.enumValues&&typeof value==="string"){let names=prop.enumValues.map(String),pascal=pascalizeIconName(value);if(!names.includes(value)&&!names.includes(pascal)){let suffix;if(REMOVED_BRAND_ICONS.has(pascal))suffix=" \u2014 lucide dropped brand glyphs; use the `SVG` component (or `Image`) for a logo";else{let near=nearestRefs(pascal,names,3).join(", ");suffix=near?`; closest: ${near}`:""}warnings.push(`${ref}: "${key}" = ${JSON.stringify(value)} doesn't match any lucide icon \u2014 renders as the fallback "?"${suffix}`)}}else if(prop.control==="enum"&&prop.enumValues&&typeof value==="string"){if(!prop.enumValues.map(String).includes(value))warnings.push(`${ref}: "${key}" = ${JSON.stringify(value)} is not one of [${prop.enumValues.join(", ")}]`)}else if(prop.control==="boolean"&&typeof value!=="boolean"&&typeof value!=="object")warnings.push(`${ref}: "${key}" expects a boolean, got ${JSON.stringify(value)}`);else if(prop.control==="number"&&typeof value!=="number"&&typeof value!=="object")warnings.push(`${ref}: "${key}" expects a number, got ${JSON.stringify(value)}`)}return warnings}async function propWarningsForTree(ctx,screen,root,basePath=[]){let out=[];async function walk(node,path){if(!isComponentNode(node))return;if(node.props){let w=await propWarnings(ctx,screen,node.$ref,node.props),prefix=path.length>0?`[${path.join(".")}] `:"";out.push(...w.map((msg)=>`${prefix}${msg}`))}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)await walk(child,[...path,i])}}return await walk(root,basePath),out}function dynamicIconWarningsForTree(root){let out=[],asComponent=(v)=>v!==null&&typeof v==="object"&&isComponentNode(v)?v:null;function walk(node,path){if(!isComponentNode(node))return;let dyn=dynamicIconName(node);if(dyn){let prefix=path.length>0?`[${path.join(".")}] `:"";out.push(`${prefix}Icon "name" is fed by ${dyn} \u2014 emit_code lowers a dynamic icon name to a single static glyph. If each instance needs its own icon, declare a \`node\` param instead (it emits as a {slot} the caller fills).`)}let propChildren=node.props?.children;if(Array.isArray(propChildren))for(let i=0;i<propChildren.length;i++){let child=asComponent(propChildren[i]);if(child)walk(child,[...path,i])}else{let single=asComponent(propChildren);if(single)walk(single,[...path,0])}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}}return walk(root,[]),out}class ParseFailure extends Error{offset;constructor(message,offset){super(message);this.offset=offset}}class DataLiteralParser{source;pos=0;constructor(source){this.source=source}parse(){let value=this.value();if(this.ws(),this.pos!==this.source.length)throw Error("unexpected token");return value}value(){this.ws();let char=this.source[this.pos];if(char==="{")return this.object();if(char==="[")return this.array();if(char==='"'||char==="'")return this.string();let tail=this.source.slice(this.pos);for(let[token,value]of[["true",!0],["false",!1],["null",null]])if(tail.startsWith(token)&&!/[A-Za-z0-9_$]/.test(tail[token.length]??""))return this.pos+=token.length,value;let number=tail.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/);if(number)return this.pos+=number[0].length,Number(number[0]);throw Error("expected a data literal")}object(){this.pos++;let out={};if(this.ws(),this.take("}"))return out;for(;;){this.ws();let char=this.source[this.pos],key=char==='"'||char==="'"?this.string():this.identifier();if(key==="__proto__"||key==="prototype"||key==="constructor")throw Error("unsafe object key");if(this.ws(),!this.take(":"))throw Error("expected colon");if(out[key]=this.value(),this.ws(),this.take("}"))return out;if(!this.take(","))throw Error("expected comma");if(this.ws(),this.take("}"))return out}}array(){this.pos++;let out=[];if(this.ws(),this.take("]"))return out;for(;;){if(out.push(this.value()),this.ws(),this.take("]"))return out;if(!this.take(","))throw Error("expected comma");if(this.ws(),this.take("]"))return out}}identifier(){let match=this.source.slice(this.pos).match(/^[A-Za-z_$][A-Za-z0-9_$]*/);if(!match)throw Error("expected object key");return this.pos+=match[0].length,match[0]}string(){let quote=this.source[this.pos];if(quote!=='"'&"e!=="'")throw Error("expected string");this.pos++;let out="";while(this.pos<this.source.length){let char=this.source[this.pos++];if(char===quote)return out;if(char!=="\\"){out+=char;continue}let escaped=this.source[this.pos++];if(escaped===void 0)throw Error("unfinished escape");let simple={b:"\b",f:"\f",n:`
|
|
437
|
+
Canvas notes are a different thing: repo-owned board artifacts for durable design guidance, created with \`add_note\`.`}},guideUri=(slug)=>`velloo://guide/${slug}`;function registerGuideResources(mcp){for(let[slug,guide]of Object.entries(GUIDES))mcp.registerResource(`guide-${slug}`,guideUri(slug),{title:guide.title,description:guide.blurb,mimeType:"text/markdown"},async(uri)=>({contents:[{uri:uri.href,mimeType:"text/markdown",text:guide.body}]}))}var read={readOnlyHint:!0,openWorldHint:!1},readRemote={readOnlyHint:!0},create={destructiveHint:!1,openWorldHint:!1},set2={destructiveHint:!1,idempotentHint:!0,openWorldHint:!1},destroy={idempotentHint:!0,openWorldHint:!1},remote={destructiveHint:!1},TOOL_ANNOTATIONS={find_nodes:read,list_designs:read,get_board:read,get_capture:read,get_comment_thread:read,get_screen:read,get_snippet:read,get_theme:read,inspect:read,list_annotations:read,list_assets:read,list_boards:read,list_captures:read,list_comment_threads:read,list_components:read,component_status:read,list_notes:read,list_screens:read,list_themes:read,score_theme_contrast:read,screenshot:read,render_snippet:read,compare_to_url:readRemote,emit_code:read,emit_snippet:read,emit_theme:{idempotentHint:!0,openWorldHint:!1},compose:{openWorldHint:!1},update_props:set2,move_node:set2,set_node_id:set2,remove_node:destroy,switch_design:set2,add_screen:create,update_screen:set2,remove_screen:destroy,add_board:create,update_board:set2,remove_board:destroy,reorder_boards:set2,add_frame:create,update_frame:set2,remove_frame:destroy,update_viewport_presets:set2,add_snippet:create,update_snippet:set2,remove_snippet:destroy,update_snippet_instance:set2,add_theme:create,set_theme:set2,update_theme:set2,remove_theme:destroy,import_theme:set2,add_note:create,update_note:set2,remove_note:destroy,add_annotation:create,update_annotation:set2,remove_annotation:destroy,update_comment_thread:set2,add_extension:create,update_extension:set2,remove_extension:destroy,import_assets:set2,upload_asset:set2,generate_asset:remote,batch:{openWorldHint:!1},start_capture_session:remote,send_feedback:remote},FACADE_TOOL_ANNOTATIONS={call_velloo:{openWorldHint:!1},run_velloo_plan:{openWorldHint:!1},operation_schema:read};function applyToolPolicy(mcp){let original=mcp.registerTool.bind(mcp),patched=(name,config,cb)=>{let input=config.inputSchema,isRawShape=input!==void 0&&input!==null&&typeof input==="object"&&typeof input.safeParse!=="function",annotations=config.annotations??TOOL_ANNOTATIONS[name]??FACADE_TOOL_ANNOTATIONS[name],finalConfig={...config,...isRawShape?{inputSchema:strictObject(input)}:{},...annotations?{annotations}:{}};return original(name,finalConfig,cb)};mcp.registerTool=patched}import{readdir as readdir2,stat}from"fs/promises";import{isAbsolute as isAbsolute3,join as join29}from"path";var MAX_BYTES=5242880;function registerAssetTools(mcp,ctx){mcp.registerTool("list_assets",{description:"List the folder's assets/ files \u2014 the `/assets/<name>` URL for <Image src>, size, whether any screen or snippet still references it, and the originating prompt for anything `generate_asset` produced. Check here before spending on art you may already have.",inputSchema:{unusedOnly:boolean().optional().describe("Only assets nothing references")}},async({unusedOnly})=>{let dir=join29(ctx.folder.root,"assets"),names=await readdir2(dir).catch(()=>[]),provenance=await readAssetsFile(ctx.folder.root),assets=[];for(let name of names.sort()){if(name.startsWith("."))continue;let info=await stat(join29(dir,name)).catch(()=>null);if(!info?.isFile())continue;let usedBy=assetReferences(ctx.folder,`assets/${name}`);if(unusedOnly&&usedBy.length>0)continue;let gen=provenance.generated[`assets/${name}`];assets.push({url:`/assets/${name}`,bytes:info.size,usedBy,...gen?{generated:{prompt:gen.prompt,intent:gen.intent,generatedAt:gen.generatedAt}}:{}})}return jsonResult({assets})}),mcp.registerTool("upload_asset",{description:"Write base64 data into assets/ and get the `/assets/<name>` URL for <Image src>. Author SVG art directly and upload it \u2014 never fall back to stock placeholders for intentional imagery. Max 5MB. For files already on disk, prefer `import_assets`.",inputSchema:{filename:string().describe('e.g. "hero-grain.svg", "cover.png"'),data:string().describe("base64-encoded file contents"),overwrite:boolean().optional().describe("Default true")}},async({filename,data,overwrite})=>{let safe=sanitizeFilename(filename);if(!isAllowedAssetExt(safe))return errorResult(`upload_asset: ${safe} is not an allowed asset type (${[...ALLOWED_ASSET_EXTENSIONS].join(", ")})`);let bytes;try{bytes=Buffer.from(data,"base64")}catch{return errorResult("upload_asset: data is not valid base64")}if(bytes.length===0||bytes.length>MAX_BYTES)return errorResult(`upload_asset: decoded size ${bytes.length} bytes is outside (0, ${MAX_BYTES}]`);let abs=join29(ctx.folder.root,"assets",safe);if(overwrite===!1&&await Bun.file(abs).exists())return errorResult(`upload_asset: ${safe} exists (overwrite: false)`);let result=await storeAsset(ctx.folder.root,safe,bytes);return jsonResult(result)}),mcp.registerTool("import_assets",{description:"Bulk-import image/SVG files into assets/ BY PATH \u2014 no base64 \u2014 returning the `/assets/<name>` URL per file. Globs are expanded relative to `baseDir` (default: the server's working dir), so `paths: ['../explore/*.png']` pulls in a whole batch. Max 5MB each; bad or missing files are reported per entry rather than failing the call.",inputSchema:{paths:array(string()).min(1).describe("Absolute or relative file paths and/or globs, e.g. ['../gen/*.png', '/abs/logo.svg']"),baseDir:string().optional().describe("Base directory for resolving relative paths and globs. Default: process cwd."),overwrite:boolean().optional().describe("Default true")}},async({paths,baseDir,overwrite})=>{let base=baseDir??process.cwd(),dir=join29(ctx.folder.root,"assets"),sources=[];for(let p of paths)if(p.includes("*")||p.includes("?")||p.includes("["))try{for await(let f of new Bun.Glob(p).scan({cwd:base,absolute:!0}))sources.push(f)}catch{}else sources.push(isAbsolute3(p)?p:join29(base,p));let results=[];for(let src of sources)try{let file=Bun.file(src);if(!await file.exists()){results.push({path:src,error:"not found"});continue}if(!isAllowedAssetExt(src)){results.push({path:src,error:"not an allowed image/font asset type"});continue}let size=file.size;if(size===0||size>MAX_BYTES){results.push({path:src,error:`size ${size} bytes outside (0, ${MAX_BYTES}]`});continue}let safe=sanitizeFilename(src);if(overwrite===!1&&await Bun.file(join29(dir,safe)).exists()){results.push({path:src,error:"exists (overwrite: false)"});continue}let stored=await storeAsset(ctx.folder.root,safe,Buffer.from(await file.arrayBuffer()));results.push({path:src,...stored})}catch(e){results.push({path:src,error:String(e.message??e)})}let imported=results.filter((r)=>("url"in r)).length;return jsonResult({imported,matched:sources.length,results})})}import{rm as rm5}from"fs/promises";import{join as join30}from"path";var asIs=(args)=>({ok:!0,args});function batchTool(name,body,normalize,run){return{body,prepare(rawArgs){let parsed=body.safeParse(rawArgs);if(!parsed.success)return{ok:!1,error:badRequest(`batch ${name}: invalid or missing args \u2014 match the standalone ${name} tool's schema.`,parsed.error.issues)};let normalized=normalize(parsed.data);if(!normalized.ok)return{ok:!1,error:badRequest(normalized.message,normalized.issues)};let args=normalized.args;return{ok:!0,run:(ctx)=>run(ctx,args)}}}}var BATCH_TOOLS={add_screen:batchTool("add_screen",AddScreenBody,asIs,addScreen2),remove_screen:batchTool("remove_screen",RemoveScreenBody,asIs,removeScreen2),set_screen_tree:batchTool("set_screen_tree",SetScreenTreeBody,asIs,setScreenTree2),add_board:batchTool("add_board",AddBoardBody,asIs,addBoard2),add_frame:batchTool("add_frame",AddFrameBody,asIs,addFrame2),remove_frame:batchTool("remove_frame",RemoveFrameBody,asIs,removeFrame2),add_node:batchTool("add_node",AddNodeBody,normalizeAddNode,addNode2),update_props:batchTool("update_props",UpdatePropsBody,asIs,updateProps2),remove_node:batchTool("remove_node",RemoveNodeBody,asIs,removeNode2),move_node:batchTool("move_node",MoveNodeBody,asIs,moveNode2),set_node_id:batchTool("set_node_id",SetNodeIdBody,asIs,setNodeId2),add_snippet:batchTool("add_snippet",AddSnippetBody,asIs,addSnippet2),update_snippet:batchTool("update_snippet",UpdateSnippetBody,asIs,updateSnippet2),remove_snippet:batchTool("remove_snippet",RemoveSnippetBody,asIs,removeSnippet2),instantiate_snippet:batchTool("instantiate_snippet",InstantiateSnippetBody,asIs,instantiateSnippet2),update_snippet_instance:batchTool("update_snippet_instance",UpdateSnippetInstanceBody,normalizeUpdateSnippetInstance,updateSnippetInstance),update_frame:batchTool("update_frame",UpdateFrameBody,asIs,updateFrames2),add_note:batchTool("add_note",AddNoteBody,asIs,addNote2)};function resourceFile(folder,kind,id){switch(kind){case"screen":return join30(folder.root,"screens",`${id}.json`);case"board":return join30(folder.root,"boards",`${id}.json`);case"snippet":return join30(folder.root,"snippets",`${id}.json`);case"notes":return join30(folder.root,"boards",`${id}.notes.json`);case"annotations":return join30(folder.root,"screens",`${id}.annotations.json`)}}function readResource(folder,kind,id){switch(kind){case"screen":return folder.screens.get(id);case"board":return folder.boards.get(id);case"snippet":return folder.snippets.get(id);case"notes":return folder.notes.get(id);case"annotations":return folder.annotations.get(id)}}function writeResourceMemory(folder,kind,id,value){switch(kind){case"screen":folder.screens.set(id,value);return;case"board":folder.boards.set(id,value);return;case"snippet":folder.snippets.set(id,value);return;case"notes":folder.notes.set(id,value);return;case"annotations":folder.annotations.set(id,value);return}}function deleteResourceMemory(folder,kind,id){switch(kind){case"screen":folder.screens.delete(id),folder.annotations.delete(id);return;case"board":folder.boards.delete(id),folder.notes.delete(id);return;case"snippet":folder.snippets.delete(id);return;case"notes":folder.notes.delete(id);return;case"annotations":folder.annotations.delete(id);return}}function touchedResources(ctx,call){let a=call.args;switch(call.tool){case"add_node":case"update_props":case"remove_node":case"move_node":case"set_node_id":case"set_screen_tree":case"update_snippet_instance":case"instantiate_snippet":{let screenId=a.screenId;if(isSnippetTreeId(screenId))return[{kind:"snippet",id:snippetIdFromTreeId(screenId)}];return[{kind:"screen",id:screenId}]}case"remove_screen":{let screenId=a.screenId,touched=[{kind:"screen",id:screenId},{kind:"annotations",id:screenId}];for(let[boardId,board]of ctx.folder.boards)if(board.frames.some((f)=>f.screen===screenId))touched.push({kind:"board",id:boardId});return touched}case"update_snippet":case"remove_snippet":return[{kind:"snippet",id:a.snippetId}];case"add_frame":case"update_frame":case"remove_frame":return[{kind:"board",id:a.boardId}];case"add_note":return[{kind:"notes",id:a.boardId}];default:return[]}}function createdResource(call,value){let v=value;switch(call.tool){case"add_screen":return{kind:"screen",id:v.screenId};case"add_board":return{kind:"board",id:v.boardId};case"add_snippet":return{kind:"snippet",id:v.snippetId};default:return null}}async function runBatch(ctx,calls,opts={}){let atomic=opts.atomic??!0,snapshots=new Map,created=[],buffered=[],undoDepth=ctx.folder.history.depths().undo,stagedCtx=atomic?{...ctx,broadcast:(e)=>buffered.push(e)}:ctx;function snapshot(kind,id){let key=`${kind}:${id}`;if(snapshots.has(key))return;let value=readResource(ctx.folder,kind,id);snapshots.set(key,{kind,id,existed:value!==void 0,value:value===void 0?void 0:structuredClone(value)})}function withResourceLock(kind,id,fn){switch(kind){case"board":case"notes":return withBoardLock(ctx.folder,id,fn);case"snippet":return withSnippetLock(ctx.folder,id,fn);default:return withScreenLock(ctx.folder,id,fn)}}async function rollback(){for(let res of created){if(snapshots.has(`${res.kind}:${res.id}`))continue;await withResourceLock(res.kind,res.id,async()=>{deleteResourceMemory(ctx.folder,res.kind,res.id),await rm5(resourceFile(ctx.folder,res.kind,res.id),{force:!0})})}for(let snap of snapshots.values())await withResourceLock(snap.kind,snap.id,async()=>{if(!snap.existed){deleteResourceMemory(ctx.folder,snap.kind,snap.id),await rm5(resourceFile(ctx.folder,snap.kind,snap.id),{force:!0});return}if(writeResourceMemory(ctx.folder,snap.kind,snap.id,structuredClone(snap.value)),(snap.kind==="notes"||snap.kind==="annotations")&&Array.isArray(snap.value)&&snap.value.length===0)await rm5(resourceFile(ctx.folder,snap.kind,snap.id),{force:!0});else await writeJsonAtomic(resourceFile(ctx.folder,snap.kind,snap.id),snap.value)});ctx.folder.history.truncateUndoTo(undoDepth)}let isIndexPath=(p)=>Array.isArray(p)||typeof p==="string"&&p.trim().startsWith("["),indexEdits=new Map;for(let call of calls){if(call.tool!=="remove_node"&&call.tool!=="move_node")continue;let a=call.args,p=a.path??a.fromPath;if(typeof a.screenId==="string"&&isIndexPath(p))indexEdits.set(a.screenId,(indexEdits.get(a.screenId)??0)+1)}let footgun=[...indexEdits].find(([,n])=>n>=2);if(footgun)return{completed:0,total:calls.length,rolledBack:atomic,results:[{tool:"batch",ok:!1,error:badRequest(`batch: ${footgun[1]} remove_node/move_node calls on screen "${footgun[0]}" use numeric paths. Indices shift as earlier siblings are removed, so later paths miss and the batch rolls back. Give each target a stable @id (set_node_id) and address it as "@id", or order removals deepest-index-first.`)}]};let results=[];for(let call of calls){let tool=BATCH_TOOLS[call.tool];if(!tool){results.push({tool:call.tool,ok:!1,error:badRequest(`batch: unsupported tool "${call.tool}"`)});break}let prepared=tool.prepare(call.args);if(!prepared.ok){results.push({tool:call.tool,ok:!1,error:prepared.error});break}if(atomic)for(let res of touchedResources(ctx,call))snapshot(res.kind,res.id);let r=await prepared.run(stagedCtx);if(r.ok){let made=createdResource(call,r.value);if(made)created.push(made);results.push({tool:call.tool,ok:!0,value:r.value})}else{results.push({tool:call.tool,ok:!1,error:r.error});break}}let failed=results.some((r)=>!r.ok);if(failed&&atomic)await rollback();else if(atomic)for(let e of buffered)ctx.broadcast(e);return{completed:results.filter((r)=>r.ok).length,total:calls.length,rolledBack:failed&&atomic,results}}var RAW_PALETTE_FAMILIES=new Set(["slate","gray","zinc","neutral","stone","red","orange","amber","yellow","lime","green","emerald","teal","cyan","sky","blue","indigo","violet","purple","fuchsia","pink","rose"]),SEMANTIC_NAMES=new Set(["background","foreground","card","card-foreground","popover","popover-foreground","primary","primary-foreground","secondary","secondary-foreground","muted","muted-foreground","accent","accent-foreground","destructive","destructive-foreground","border","input","ring"]),COLOR_NEUTRAL_LITERALS=new Set(["transparent","current","inherit"]),FIXED_COLOR_LITERALS=new Set(["white","black"]),COLOR_CAPABLE_PREFIXES=["bg","text","border","ring","outline","divide","from","via","to","fill","stroke","shadow","decoration","accent","caret","placeholder"],VARIANT_RE=/^([^:]+:)+/,OPACITY_SUFFIX_RE=/\/[0-9]+$/;function stripDecorations(cls){let m=cls.match(VARIANT_RE),variantPrefix=m?m[0]:"";return{core:cls.slice(variantPrefix.length).replace(OPACITY_SUFFIX_RE,""),variantPrefix}}function looksLikeArbitraryColor(value){return/^#[0-9a-fA-F]{3,8}$/.test(value)||/^(rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|color)\(/i.test(value)}function parseColorClass(cls){let{core,variantPrefix}=stripDecorations(cls);for(let prefix of COLOR_CAPABLE_PREFIXES){let head=`${prefix}-`;if(!core.startsWith(head))continue;let rest=core.slice(head.length);if(rest.length===0)continue;if(rest.startsWith("[")&&rest.endsWith("]")){let inner=rest.slice(1,-1);if(!looksLikeArbitraryColor(inner))return null;return{raw:cls,variantPrefix,prefix,family:"arbitrary",isPalette:!1,isSemantic:!1,isLiteral:!1,isArbitraryColor:!0}}let firstSeg=rest.split("-")[0]??rest;if(COLOR_NEUTRAL_LITERALS.has(rest)||COLOR_NEUTRAL_LITERALS.has(firstSeg))return null;if(FIXED_COLOR_LITERALS.has(rest)||FIXED_COLOR_LITERALS.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!1,isSemantic:!1,isLiteral:!0,isArbitraryColor:!1};if(RAW_PALETTE_FAMILIES.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!0,isSemantic:!1,isLiteral:!1,isArbitraryColor:!1};if(SEMANTIC_NAMES.has(rest))return{raw:cls,variantPrefix,prefix,family:rest,isPalette:!1,isSemantic:!0,isLiteral:!1,isArbitraryColor:!1};if(SEMANTIC_NAMES.has(firstSeg))return{raw:cls,variantPrefix,prefix,family:firstSeg,isPalette:!1,isSemantic:!0,isLiteral:!1,isArbitraryColor:!1};return null}return null}function suggestSemanticFor(parsed){if(!parsed.isPalette&&!parsed.isLiteral)return null;let{variantPrefix,prefix,family,raw}=parsed;if(prefix==="bg"){if(family==="white"||parsed.isPalette&&raw.endsWith("-50"))return`${variantPrefix}bg-card`;if(family==="black"||parsed.isPalette&&/-95\d?$/.test(raw))return`${variantPrefix}bg-background`;if(parsed.isPalette&&/-(900|800|700)$/.test(raw))return`${variantPrefix}bg-muted`}if(prefix==="text"){if(family==="black"||parsed.isPalette&&/-(900|800)$/.test(raw))return`${variantPrefix}text-foreground`;if(family==="white"||parsed.isPalette&&/-(50|100)$/.test(raw))return`${variantPrefix}text-foreground`;if(parsed.isPalette&&/-(400|500|600)$/.test(raw))return`${variantPrefix}text-muted-foreground`}if((prefix==="border"||prefix==="divide")&&parsed.isPalette)return`${variantPrefix}${prefix}-border`;if(prefix==="ring"&&parsed.isPalette)return`${variantPrefix}ring-ring`;return null}function auditClasses(className){let tokens=className.split(/\s+/).filter(Boolean),raw=[],suggestions={},hasColor=!1,allSemantic=!0;for(let token of tokens){let parsed=parseColorClass(token);if(!parsed)continue;if(hasColor=!0,parsed.isSemantic)continue;raw.push(token),allSemantic=!1;let suggestion=suggestSemanticFor(parsed);if(suggestion)suggestions[token]=suggestion}return{raw,suggestions,hasColor,allSemantic}}function darkModeAuditTree(root){let problems=[],totalColored=0,semanticCount=0;function walk(node,path){if(isComponentNode(node)){let accentProp=node.props?.["data-accent"];if(!(accentProp!==void 0&&accentProp!==null&&accentProp!==!1&&accentProp!=="")){let className=typeof node.props?.className==="string"?node.props.className:"";if(className){let{raw,suggestions,hasColor,allSemantic}=auditClasses(className);if(hasColor){if(totalColored++,allSemantic)semanticCount++;if(raw.length>0)problems.push({path:[...path],ref:node.$ref,className,raw,suggestions})}}}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}}else if(isSnippetInstance(node));}return walk(root,[]),{coverage:totalColored===0?1:semanticCount/totalColored,problems,totalColored}}import{__unstable__loadDesignSystem}from"@tailwindcss/node";var designSystemPromise=null,designSystemKey=null;function getDesignSystem(jit){return jit.entryCss().then(({css,base})=>{if(designSystemPromise&&designSystemKey===css)return designSystemPromise;return designSystemKey=css,designSystemPromise=__unstable__loadDesignSystem(css,{base}),designSystemPromise})}function definedInCustomCss(customCss,cls){if(!customCss||!/^[a-z][a-z0-9:_-]*$/i.test(cls))return!1;let escaped=cls.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`\\.${escaped}(?![\\w-])`).test(customCss)}function referencedVars(css){let out=new Set,re=/var\(\s*(--[a-zA-Z0-9-]+)/g,m=re.exec(css);while(m!==null)out.add(m[1]),m=re.exec(css);return[...out]}function declaredVars(css){let out=new Set,decl=/(--[a-zA-Z0-9-]+)\s*:/g,m=decl.exec(css);while(m!==null)out.add(m[1]),m=decl.exec(css);let prop=/@property\s+(--[a-zA-Z0-9-]+)/g;m=prop.exec(css);while(m!==null)out.add(m[1]),m=prop.exec(css);return out}function unresolvedVars(css,theme){let declared=declaredVars(css),out=[];for(let name of referencedVars(css)){if(declared.has(name))continue;if(name.startsWith("--tw-"))continue;if(theme.get([name])===null)out.push(name)}return out}async function validateClassNames(jit,customCss,classes){let ds=await getDesignSystem(jit);return classes.map((cls)=>{let trimmed=cls.trim();if(trimmed==="")return{class:cls,valid:!1,reason:"empty class"};try{let parsed=ds.parseCandidate(trimmed);if(parsed.length>0){let generated=ds.candidatesToCss([trimmed])[0];if(generated!==null&&generated!==void 0){let dangling=unresolvedVars(generated,ds.theme);if(dangling.length>0)return{class:cls,valid:!0,warning:`resolves to undefined CSS var ${dangling.join(", ")}; will fall back at runtime`};return{class:cls,valid:!0}}}if(definedInCustomCss(customCss,trimmed))return{class:cls,valid:!0,reason:"defined in custom_css (not a Tailwind utility)"};return{class:cls,valid:!1,reason:parsed.length===0?"no matching Tailwind utility":"candidate produced no CSS"}}catch(err){if(definedInCustomCss(customCss,trimmed))return{class:cls,valid:!0,reason:"defined in custom_css (not a Tailwind utility)"};return{class:cls,valid:!1,reason:err instanceof Error?err.message:String(err)}}})}function classUses(root,prefix){let out=[],walk=(node,path)=>{if(!isComponentNode(node))return;let className=typeof node.props?.className==="string"?node.props.className.trim():"";if(className)out.push({path,className,classes:className.split(/\s+/).filter(Boolean)});for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}};return walk(root,prefix),out}var RAW_COLOR_LIMIT=8;async function diagnosticsForTree(ctx,jit,thing,root,prefix=[]){if(!jit)return[];if(!styleChannelOf(providerForScreen2(ctx,thing),ctx.folder.config.styling?.framework).needsTailwindJit)return[];let uses=classUses(root,prefix),unique=[...new Set(uses.flatMap((use)=>use.classes))],reports=unique.length?await validateClassNames(jit,ctx.folder.customCss,unique):[],reportByClass=new Map(reports.map((report)=>[report.class,report])),diagnostics=[];for(let use of uses)for(let cls of use.classes){let report=reportByClass.get(cls);if(!report)continue;if(!report.valid)diagnostics.push({severity:"warning",code:"tailwind/invalid-class",path:use.path,message:`\`${cls}\` does not compile: ${report.reason??"no matching utility"}`});else if(report.warning)diagnostics.push({severity:"warning",code:"tailwind/undefined-var",path:use.path,message:`\`${cls}\` ${report.warning}`})}let hostRoot=hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp);if(detectTailwindMajor(hostRoot)===3){let issues=new Map(v3ClassIssues(unique).map((issue)=>[issue.class,issue]));for(let use of uses)for(let cls of use.classes){let issue=issues.get(cls);if(!issue)continue;diagnostics.push({severity:"warning",code:"tailwind/v3",path:use.path,message:`\`${cls}\` uses Tailwind v4 semantics in a v3 host: ${issue.note}`,...issue.v3?{suggestion:issue.v3}:{}})}}return diagnostics.push(...rawColorDiagnostics(root,prefix)),diagnostics}function rawColorDiagnostics(root,prefix=[]){let problems=darkModeAuditTree(root).problems,out=problems.slice(0,RAW_COLOR_LIMIT).map((problem)=>({severity:"warning",code:"theme/raw-color",path:[...prefix,...problem.path],message:`${problem.ref} uses non-flipping color classes: ${problem.raw.join(", ")}`,...Object.keys(problem.suggestions).length?{suggestion:Object.entries(problem.suggestions).map(([from,to])=>`${from} \u2192 ${to}`).join(", ")}:{}}));if(problems.length>RAW_COLOR_LIMIT)out.push({severity:"warning",code:"theme/raw-color",path:[...prefix],message:`${problems.length-RAW_COLOR_LIMIT} more node(s) use non-flipping color classes. `+"If these are deliberate \u2014 text over an image or scrim, a brand accent, chrome tuned for one mode \u2014 "+'set `data-accent: "ok"` on each to exempt it, rather than taking a suggestion that would break the design. The opt-out does not cascade to children.'});return out}function pathsUsing(root,ref){let out=[],walk=(node,path)=>{if(!isComponentNode(node))return;if(node.$ref===ref)out.push(path);for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}};return walk(root,[]),out}function renderDiagnostics(ctx,screen){let failures;try{failures=renderBodyGuarded(screen,registryForScreen2(ctx,screen),ctx.folder.snippets).failures}catch{return[]}return failures.flatMap((failure)=>{let paths=pathsUsing(screen.tree,failure.componentId);return(paths.length>0?paths:[[]]).map((path)=>({severity:"error",code:"render/component-threw",path,message:`\`${failure.componentId}\` failed to render and was replaced with a placeholder: ${failure.reason}`}))})}async function diagnosticsForScreen(ctx,jit,screen){return[...renderDiagnostics(ctx,screen),...await diagnosticsForTree(ctx,jit,screen,screen.tree)]}var hiddenTreeTools=new Set(["add_node","set_screen_tree","instantiate_snippet"]),batchableTools=Object.keys(BATCH_TOOLS).filter((name)=>!hiddenTreeTools.has(name));function registerBatchTool(mcp,ctx,jit){mcp.registerTool("batch",{description:"Run a sequence of mutation calls in one round-trip; each entry is `{ tool, args }` with that tool's own arguments. Atomic by default \u2014 the first error rolls every touched resource back to its pre-batch state and reports `rolledBack: true` with the failing call. `atomic: false` runs until error and keeps completed work.",annotations:{readOnlyHint:!1,destructiveHint:!0,idempotentHint:!1},inputSchema:{calls:array(strictObject({tool:_enum(batchableTools),args:record(string(),unknown())})).min(1).max(100),atomic:boolean().optional().describe("Default true")}},async({calls,atomic})=>{let{value:result,ops}=await collectActivity(()=>runBatch(ctx,calls,{atomic}));if(!result.rolledBack)emitGroupedActivity(ctx,"batch",ops);let failed=result.results.some((r)=>!r.ok),diagnostics={};if(!result.rolledBack&&jit){let screenIds=new Set,snippetIds=new Set;for(let call of calls){if(typeof call.args.screenId==="string")screenIds.add(call.args.screenId);if(typeof call.args.snippetId==="string")snippetIds.add(call.args.snippetId);if(call.tool==="add_screen"&&typeof call.args.id==="string")screenIds.add(call.args.id);if(call.tool==="add_snippet"&&typeof call.args.id==="string")snippetIds.add(call.args.id)}for(let screenId of screenIds){let screen=ctx.folder.screens.get(screenId);if(!screen)continue;let found=await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]);if(found.length>0)diagnostics[`screen:${screenId}`]=found}for(let snippetId of snippetIds){let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)continue;let found=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);if(found.length>0)diagnostics[`snippet:${snippetId}`]=found}}return{...failed?{isError:!0}:{},content:[{type:"text",text:JSON.stringify({...result,...Object.keys(diagnostics).length>0?{diagnostics}:{}})}]}})}import{readFileSync as readFileSync8}from"fs";import{join as join31}from"path";var sessions=new Map;function activeSessions(){return[...sessions.values()].filter((s)=>s.endedAt===null)}function sessionStatuses(list=sessions.values()){return[...list].map((session)=>{let open=session.endedAt===null,read=(get)=>{try{return get()}catch{return null}};return{sessionId:session.handle.sessionId,status:open?"open":"closed",startedUrl:session.url??null,currentUrl:open?read(()=>session.handle.currentUrl()):null,openedAt:new Date(session.startedAt).toISOString(),endedAt:session.endedAt===null?null:new Date(session.endedAt).toISOString(),captured:(read(()=>session.handle.captures())??[]).map((m)=>({captureId:m.id,url:m.finalUrl||m.url}))}})}async function openSession(ctx,opts){let existing=activeSessions()[0];if(existing)throw Error(`a capture session is already open (${existing.handle.sessionId})${existing.url?` on ${existing.url}`:""} \u2014 finish it in the browser window before starting another`);let folderId=ctx.folder.config.folderId,statePath=opts.url?sessionStatePath(ctx.folder.root,new URL(opts.url).origin,folderId):void 0,handle=await startCaptureSession({folderRoot:ctx.folder.root,...folderId?{folderId}:{},...opts.url?{url:opts.url}:{},...statePath?{sessionStatePath:statePath}:{},...opts.onCapture?{onCapture:opts.onCapture}:{},...opts.onStatus?{onStatus:opts.onStatus}:{}}),session={handle,url:opts.url,startedAt:Date.now(),endedAt:null};return sessions.set(handle.sessionId,session),handle.finished.then(()=>{session.endedAt=Date.now()}),session}function jsonResult2(value){return{content:[{type:"text",text:JSON.stringify(value)}]}}function readBytes(path,length=32){try{return readFileSync8(path).subarray(0,length)}catch{return Buffer.alloc(0)}}function invisibleNodes(dom,verticalScope){let hidden=new Set,offscreen=new Set;for(let n of dom.nodes){if(n.parent!==null&&hidden.has(n.parent))hidden.add(n.i);else if(n.style.opacity!==void 0&&Number(n.style.opacity)===0)hidden.add(n.i);let{x,y,w,h}=n.rect;if(x+w<=0||y+h<=(verticalScope?.from??0)||x>=dom.viewport.w||verticalScope!==void 0&&y>=verticalScope.to)offscreen.add(n.i)}return{hidden,offscreen}}function meaningfulNodes(dom){return dom.nodes.filter((n)=>n.repeat!==void 0||n.text!==void 0&&n.text.length>1||["header","nav","main","section","footer","aside","form","table"].includes(n.tag))}function spatialSample(nodes,dom,limit){if(nodes.length<=limit)return nodes;let selected=new Set,viewportHeight=Math.max(1,dom.viewport.h),bandCount=Math.max(1,Math.ceil(dom.documentHeight/viewportHeight)),bandBudget=Math.min(bandCount,Math.max(1,Math.floor(limit/2)));for(let slot=0;slot<bandBudget;slot++){let band=Math.min(bandCount-1,Math.floor(slot*bandCount/bandBudget)),inBand=nodes.filter((n)=>Math.floor(Math.max(0,n.rect.y)/viewportHeight)===band),pick=inBand.find((n)=>n.repeat!==void 0)??inBand.find((n)=>["header","nav","main","section","footer","aside"].includes(n.tag))??inBand[0];if(pick)selected.add(pick.i)}let remaining=limit-selected.size;for(let slot=0;slot<remaining;slot++){let index=Math.min(nodes.length-1,Math.floor(slot*nodes.length/remaining)),pick=nodes[index];if(pick)selected.add(pick.i)}if(selected.size<limit){for(let node of nodes)if(selected.add(node.i),selected.size>=limit)break}return nodes.filter((n)=>selected.has(n.i)).slice(0,limit)}function outlineOf(dom,limit=60,verticalScope){let{hidden,offscreen}=invisibleNodes(dom,verticalScope),lines=[],sampled=spatialSample(meaningfulNodes(dom),dom,limit);for(let n of sampled){let indent=" ".repeat(Math.min(n.depth,8)),rep=n.repeat?` \xD7${n.repeat.count}`:"",text=n.text?` "${n.text.slice(0,60)}"`:"",flags=[];if(hidden.has(n.i))flags.push(Number(n.style.opacity)===0?"HIDDEN(opacity:0)":"HIDDEN");if(offscreen.has(n.i))flags.push("OFFSCREEN");let marks=flags.length?` ${flags.join(" ")}`:"",{x,y,w,h}=n.rect;lines.push(`${indent}${n.tag}${rep}${text} [${w}\xD7${h} @${x},${y}]${marks}`)}let ys=sampled.flatMap((n)=>[n.rect.y,n.rect.y+n.rect.h]);return{lines,shown:lines.length,hiddenCount:hidden.size+offscreen.size,coverage:{fromY:ys.length?Math.min(...ys):0,toY:ys.length?Math.max(...ys):0,documentHeight:dom.documentHeight}}}function queriedNodes(dom,input){let{hidden,offscreen}=invisibleNodes(dom,input.verticalScope),tags=input.tags?new Set(input.tags.map((tag)=>tag.toLowerCase())):null,matches=dom.nodes.filter((node)=>{if(tags&&!tags.has(node.tag))return!1;if(input.visibleOnly&&(hidden.has(node.i)||offscreen.has(node.i)))return!1;if(input.yFrom!==void 0&&node.rect.y+node.rect.h<=input.yFrom)return!1;if(input.yTo!==void 0&&node.rect.y>=input.yTo)return!1;return!0}),offset=input.offset??0,limit=input.limit??200,nodes=matches.slice(offset,offset+limit),next=offset+nodes.length;return{nodes,total:matches.length,offset,nextOffset:next<matches.length?next:null}}function previewOf(png,manifest,input){let bitmap=pngSize2(png);if(!bitmap)return{error:"capture preview is not a readable PNG"};let viewportWidth=manifest.geometry?.viewportCss.width??manifest.viewport.w,viewportHeight=manifest.geometry?.viewportCss.height??manifest.viewport.h,bitmapScale=viewportWidth>0?bitmap.width/viewportWidth:1,out=png,cssRegion=null;if(input.mode!=="overview"){let scroll=manifest.geometry?.scrollCss??{x:0,y:0},sourceIsViewport=manifest.geometry?.screenshot?.mode==="viewport",x=input.mode==="viewport"?scroll.x:input.xFrom??0,y=input.mode==="viewport"?scroll.y:input.yFrom??0,xTo=input.mode==="viewport"?x+viewportWidth:input.xTo??viewportWidth,yTo=input.mode==="viewport"?y+viewportHeight:input.yTo??y+viewportHeight;if(xTo<=x||yTo<=y)return{error:"capture preview crop must have positive width and height"};if(cssRegion={x,y,w:xTo-x,h:yTo-y},!(input.mode==="viewport"&&sourceIsViewport)){let origin=sourceIsViewport?scroll:{x:0,y:0},pixelRegion={x:Math.round((x-origin.x)*bitmapScale),y:Math.round((y-origin.y)*bitmapScale),w:Math.round((xTo-x)*bitmapScale),h:Math.round((yTo-y)*bitmapScale)};if(pixelRegion.x>=bitmap.width||pixelRegion.y>=bitmap.height||pixelRegion.x+pixelRegion.w<=0||pixelRegion.y+pixelRegion.h<=0)return{error:"capture preview crop does not intersect page.png"};let left=Math.max(0,pixelRegion.x),top=Math.max(0,pixelRegion.y),right=Math.min(bitmap.width,pixelRegion.x+pixelRegion.w),bottom=Math.min(bitmap.height,pixelRegion.y+pixelRegion.h);out=cropPng(png,{x:left,y:top,w:right-left,h:bottom-top},0),cssRegion={x:origin.x+left/bitmapScale,y:origin.y+top/bitmapScale,w:(right-left)/bitmapScale,h:(bottom-top)/bitmapScale}}}let size=pngSize2(out);if(!size)return{error:"capture preview crop is not a readable PNG"};let maxWidth=input.maxWidth??1400,maxPixels=4000000,factor=Math.max(1,Math.ceil(size.width/maxWidth),Math.ceil(Math.sqrt(size.width*size.height/maxPixels)));if(factor>1)out=downscalePng(out,factor);while(out.byteLength>3500000){let current=pngSize2(out);if(!current||current.width===1&¤t.height===1)break;out=downscalePng(out,2),factor*=2}let finalSize=pngSize2(out);return{png:out,meta:{mode:input.mode,bitmap:finalSize,sourceBitmap:bitmap,bitmapScale,downscaleFactor:factor,...cssRegion?{cssRegion}:{}}}}function captureCapabilities(manifest){if(manifest.themeOnly)return["theme-import"];if(manifest.stability?.status==="unstable")return["preview"];return["theme-import","outline","comparison","preview"]}function registerCaptureTools(mcp,ctx){let folderId=()=>ctx.folder.config.folderId;mcp.registerTool("start_capture_session",{description:"Open a real browser window the USER drives, to reach a page behind a login or on staging. Returns a sessionId IMMEDIATELY \u2014 never treat it as blocking and never re-call it to check; poll list_captures instead, and tell the user to log in, hit **Capture page** per page, then **Done**. Guide: velloo://guide/capture.",inputSchema:{url:string().optional().describe("Page to open the browser on. The session is scoped to this origin.")}},async({url})=>{if(url!==void 0){let scheme;try{scheme=new URL(url).protocol}catch{return errorResult(`Invalid url: ${url}`)}if(scheme!=="http:"&&scheme!=="https:")return errorResult(`start_capture_session: only http(s) URLs are allowed (got ${scheme})`)}try{let session=await openSession(ctx,{...url?{url}:{},onStatus:(message)=>{console.error(`velloo capture: ${message}`)}});return emitActivity(ctx,"start_capture_session",{}),console.error(`velloo capture: an agent opened a capture session${url?` on ${url}`:""} \u2014 the browser window is yours to drive.`),jsonResult2({sessionId:session.handle.sessionId,url:url??null,status:"open",note:"The browser window is open and the user is driving it. This call did NOT wait \u2014 poll list_captures, whose `sessions[]` reports whether the window is still open and what page the user is on, and tell them to hit 'Capture page' in the toolbar on each page you need, then 'Done'."})}catch(err){if(err instanceof HeadedBrowserMissingError)return errorResult(err.message);return errorResult(`start_capture_session failed: ${err instanceof Error?err.message:String(err)}`)}}),mcp.registerTool("list_captures",{description:"Stored browser captures for this folder, newest first, plus open session state. `kind`, `purpose`, and `usableFor` distinguish automatic theme snapshots from user-requested frozen pages; geometry names CSS, bitmap, viewport, DPR, and replay sizes separately. Read a capture with `get_capture`; compare a page capture via `compare_to_url { source: { captureId } }`. Poll `sessions[]` rather than re-calling `start_capture_session`.",inputSchema:{}},async()=>{let captures=listCaptures(ctx.folder.root,folderId()).map((m)=>({captureId:m.id,url:m.finalUrl||m.url,title:m.title,capturedAt:m.capturedAt,viewport:m.viewport,themeOnly:m.themeOnly,kind:m.kind??(m.themeOnly?"theme":"page"),purpose:m.themeOnly?"Automatic theme snapshot; use for import_theme, not visual comparison.":"User-requested frozen page; use for outline, preview, and comparison.",usableFor:captureCapabilities(m),...m.sessionId?{sessionId:m.sessionId}:{},...m.stability?{stability:m.stability.status}:{},...m.geometry?{geometry:m.geometry}:{},nodeCount:m.nodeCount,assetCount:m.assetCount})),sessions=sessionStatuses(),open=sessions.find((s)=>s.status==="open");return jsonResult2({captures,sessions,...open?{note:`A capture session is open${open.currentUrl?` on ${open.currentUrl}`:""}. The user drives it: ask them to reach each page you need and hit 'Capture page', then 'Done'. Poll this tool \u2014 do not call start_capture_session again.`}:captures.length===0?{note:sessions.length?"The capture session has closed and produced nothing. Ask the user whether they hit 'Capture page' before 'Done', then start another session.":"No captures yet. `start_capture_session` opens a browser for the user to log in and capture pages from."}:{}})}),mcp.registerTool("get_capture",{description:"Read one stored capture: a spatial page outline with rects, visibility markers, repeated blocks, theme CSS, assets, and explicit CSS/bitmap/replay geometry. Use `image` for a bounded inline overview, viewport, or crop of authoritative `page.png`; use `full: true` or y/tag filters with offset + limit for paginated computed-style nodes. Feed the theme CSS to `import_theme` BEFORE composing. Guide: velloo://guide/capture.",inputSchema:{captureId:string(),full:boolean().optional().describe("Return the first paginated node page with computed styles. Default false."),yFrom:number().nonnegative().optional().describe("Only nodes crossing this CSS-pixel Y."),yTo:number().positive().optional().describe("Only nodes before this CSS-pixel Y."),tags:array(string().min(1)).optional(),visibleOnly:boolean().optional(),offset:number().int().nonnegative().optional(),limit:number().int().min(1).max(500).optional().describe("Default 200, max 500."),image:strictObject({mode:_enum(["overview","viewport","crop"]),xFrom:number().nonnegative().optional(),xTo:number().positive().optional(),yFrom:number().nonnegative().optional(),yTo:number().positive().optional(),maxWidth:number().int().min(100).max(2000).optional()}).optional().describe("Return a bounded inline PNG overview, captured viewport, or CSS-coordinate crop.")}},async({captureId,full,yFrom,yTo,tags,visibleOnly,offset,limit,image})=>{if(!isSafeCaptureId(captureId))return errorResult(`Invalid captureId: ${captureId}`);let manifest=readCaptureManifest(ctx.folder.root,captureId,folderId());if(!manifest)return errorResult(`No capture "${captureId}" for this folder \u2014 call list_captures.`);let dir=captureDir(ctx.folder.root,captureId,folderId()),readJson=(file)=>{try{return JSON.parse(readFileSync8(join31(dir,file),"utf8"))}catch{return null}},theme=readJson("computed-vars.json"),dom=manifest.themeOnly?null:readJson("dom.json"),assets=readJson("assets.json"),verticalScope=manifest.geometry?.screenshot?.mode==="viewport"?{from:manifest.geometry.scrollCss.y,to:manifest.geometry.scrollCss.y+manifest.geometry.viewportCss.height}:void 0,outline=dom?outlineOf(dom,60,verticalScope):null,files=manifest.files.map((name)=>{let path=join31(dir,name),size=name.endsWith(".png")?pngSize2(readBytes(path)):null;return{name,path,...size??{}}}),screenshot=files.find((f)=>f.name==="page.png"),wantsNodes=Boolean(full||yFrom!==void 0||yTo!==void 0||tags||visibleOnly||offset||limit),nodePage=dom&&wantsNodes?queriedNodes(dom,{...yFrom!==void 0?{yFrom}:{},...yTo!==void 0?{yTo}:{},...tags!==void 0?{tags}:{},...visibleOnly!==void 0?{visibleOnly}:{},...offset!==void 0?{offset}:{},...limit!==void 0?{limit}:{},...verticalScope!==void 0?{verticalScope}:{}}):null,body={captureId,url:manifest.finalUrl||manifest.url,title:manifest.title,capturedAt:manifest.capturedAt,viewport:manifest.viewport,themeOnly:manifest.themeOnly,kind:manifest.kind??(manifest.themeOnly?"theme":"page"),usableFor:captureCapabilities(manifest),...manifest.stateId?{stateId:manifest.stateId}:{},...manifest.geometry?{geometry:manifest.geometry}:{},...manifest.stability?{stability:manifest.stability}:{},...manifest.stability?.status==="unstable"?{warning:"The page changed while screenshot and DOM evidence were recorded. Preview is available, but re-capture after the page settles before using its outline or comparison."}:{},...theme?{themeCss:theme.css,fonts:theme.fonts,tokenCount:Object.keys(theme.light).length+Object.keys(theme.dark).length}:{},...dom&&outline?nodePage?{documentHeight:dom.documentHeight,nodes:nodePage.nodes,nodeQuery:{total:nodePage.total,returned:nodePage.nodes.length,offset:nodePage.offset,nextOffset:nodePage.nextOffset},truncated:dom.truncated}:{documentHeight:dom.documentHeight,outline:outline.lines,outlineShown:outline.shown,outlineCoverage:outline.coverage,nodeCount:dom.nodes.length,truncated:dom.truncated,note:`Digest of ${outline.shown} of ${dom.nodes.length} nodes \u2014 rects are `+"`[w\xD7h @x,y]` in page coordinates. "+(outline.hiddenCount>0?`${outline.hiddenCount} node(s) are marked HIDDEN or OFFSCREEN: they lay out but do not appear in page.png, so a hover-only or off-stage block is NOT ordinary page content \u2014 model it as such or leave it out. `:"")+(screenshot?`page.png (${screenshot.width}\xD7${screenshot.height}) is the authoritative reference \u2014 open it. `:"")+"Use full: true or yFrom/yTo/tags with offset + limit for paginated computed styles."}:{},...assets?{assets:assets.assets}:{},dir,files,...screenshot?{verifyWith:`compare_to_url { screenId: "<your screen>", source: { captureId: "${captureId}" } }`}:{}};if(!image)return jsonResult2(body);if(!screenshot)return errorResult(`Capture "${captureId}" has no page.png to preview.`);let preview=previewOf(readFileSync8(screenshot.path),manifest,{mode:image.mode,...image.xFrom!==void 0?{xFrom:image.xFrom}:{},...image.xTo!==void 0?{xTo:image.xTo}:{},...image.yFrom!==void 0?{yFrom:image.yFrom}:{},...image.yTo!==void 0?{yTo:image.yTo}:{},...image.maxWidth!==void 0?{maxWidth:image.maxWidth}:{}});if("error"in preview)return errorResult(preview.error);return{content:[{type:"text",text:JSON.stringify({...body,preview:preview.meta})},{type:"image",data:preview.png.toString("base64"),mimeType:"image/png"}]}})}async function result(operation){try{return jsonResult(await operation())}catch(error){if(error instanceof CommentStoreError)return errorResult({kind:"CommentStore",code:error.code,message:error.message});throw error}}function registerCommentTools(mcp,comments){mcp.registerTool("list_comment_threads",{description:"List visual feedback threads. Every open thread is work waiting on you \u2014 there is no separate inbox. Returns all open threads across the folder by default; `boardId` narrows, `scope` separates local threads from cloud ones left on a published link. Anchors report their attached path, or stale when the original node is gone.",inputSchema:{boardId:string().optional(),status:_enum(["open","resolved","all"]).optional(),scope:_enum(["local","shared","all"]).optional()}},async({boardId,status="open",scope="all"})=>result(async()=>({threads:boardId?await comments.list(boardId,status,scope):await comments.listAll(status,scope)}))),mcp.registerTool("get_comment_thread",{description:"Read one complete visual feedback thread, including its anchor and replies.",inputSchema:{threadId:string().uuid()}},async({threadId})=>result(async()=>({thread:await comments.get(threadId)}))),mcp.registerTool("update_comment_thread",{description:'Act on a feedback thread: `reply` posts an agent message, `status` moves it to resolved / open / deleted. Both in one call is the normal close-out \u2014 reply saying what changed, then resolve. `status: "deleted"` is permanent; only on the user\'s explicit ask. Guide: velloo://guide/comments.',inputSchema:{threadId:string().uuid(),reply:string().trim().min(1).max(4000).optional().describe("Agent message to post"),status:_enum(["resolved","open","deleted"]).optional()}},async({threadId,reply,status})=>{if(reply===void 0&&status===void 0)return errorResult({kind:"BadRequest",message:"update_comment_thread: pass reply, status, or both."});return result(async()=>{let thread=reply?await comments.reply(threadId,{body:reply,author:{kind:"agent",displayName:"Agent"}}):void 0;if(status==="deleted")return comments.delete(threadId);if(status!==void 0)thread=await comments.setResolved(threadId,status==="resolved");return{thread:thread??await comments.get(threadId)}})})}var UNIVERSAL_PROPS=new Set(["className","children","id","style","title","role"]),manifestCache=new WeakMap;function manifestFor(provider){let cached=manifestCache.get(provider);if(!cached)cached=provider.loadManifest().catch(()=>[]),manifestCache.set(provider,cached);return cached}function isSubstitution(value){return typeof value==="object"&&value!==null&&(typeof value.$param==="string"||typeof value.$if==="string")}async function propWarnings(ctx,screen,ref,props){let provider=providerForScreen(screen,ctx.providers,ctx.defaultProvider),descriptor=(await manifestFor(provider)).find((c)=>c.id===ref),extension=ctx.folder.config.extensions?.[ref],known=descriptor?.props??extension?.props;if(!known||known.length===0)return[];let warnings=[],knownNames=known.map((p)=>p.name);for(let[key,value]of Object.entries(props)){if(UNIVERSAL_PROPS.has(key)||key.startsWith("data-")||key.startsWith("aria-"))continue;if(isSubstitution(value))continue;let prop=known.find((p)=>p.name===key);if(!prop){let near=nearestRefs(key,knownNames,3).join(", ");warnings.push(`${ref}: unknown prop "${key}"${near?` \u2014 closest known: ${near}`:""}`);continue}if(prop.control==="icon"&&prop.enumValues&&typeof value==="string"){let names=prop.enumValues.map(String),pascal=pascalizeIconName(value);if(!names.includes(value)&&!names.includes(pascal)){let suffix;if(REMOVED_BRAND_ICONS.has(pascal))suffix=" \u2014 lucide dropped brand glyphs; use the `SVG` component (or `Image`) for a logo";else{let near=nearestRefs(pascal,names,3).join(", ");suffix=near?`; closest: ${near}`:""}warnings.push(`${ref}: "${key}" = ${JSON.stringify(value)} doesn't match any lucide icon \u2014 renders as the fallback "?"${suffix}`)}}else if(prop.control==="enum"&&prop.enumValues&&typeof value==="string"){if(!prop.enumValues.map(String).includes(value))warnings.push(`${ref}: "${key}" = ${JSON.stringify(value)} is not one of [${prop.enumValues.join(", ")}]`)}else if(prop.control==="boolean"&&typeof value!=="boolean"&&typeof value!=="object")warnings.push(`${ref}: "${key}" expects a boolean, got ${JSON.stringify(value)}`);else if(prop.control==="number"&&typeof value!=="number"&&typeof value!=="object")warnings.push(`${ref}: "${key}" expects a number, got ${JSON.stringify(value)}`)}return warnings}async function propWarningsForTree(ctx,screen,root,basePath=[]){let out=[];async function walk(node,path){if(!isComponentNode(node))return;if(node.props){let w=await propWarnings(ctx,screen,node.$ref,node.props),prefix=path.length>0?`[${path.join(".")}] `:"";out.push(...w.map((msg)=>`${prefix}${msg}`))}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)await walk(child,[...path,i])}}return await walk(root,basePath),out}function dynamicIconWarningsForTree(root){let out=[],asComponent=(v)=>v!==null&&typeof v==="object"&&isComponentNode(v)?v:null;function walk(node,path){if(!isComponentNode(node))return;let dyn=dynamicIconName(node);if(dyn){let prefix=path.length>0?`[${path.join(".")}] `:"";out.push(`${prefix}Icon "name" is fed by ${dyn} \u2014 emit_code lowers a dynamic icon name to a single static glyph. If each instance needs its own icon, declare a \`node\` param instead (it emits as a {slot} the caller fills).`)}let propChildren=node.props?.children;if(Array.isArray(propChildren))for(let i=0;i<propChildren.length;i++){let child=asComponent(propChildren[i]);if(child)walk(child,[...path,i])}else{let single=asComponent(propChildren);if(single)walk(single,[...path,0])}for(let i=0;i<(node.children?.length??0);i++){let child=node.children?.[i];if(child)walk(child,[...path,i])}}return walk(root,[]),out}class ParseFailure extends Error{offset;constructor(message,offset){super(message);this.offset=offset}}class DataLiteralParser{source;pos=0;constructor(source){this.source=source}parse(){let value=this.value();if(this.ws(),this.pos!==this.source.length)throw Error("unexpected token");return value}value(){this.ws();let char=this.source[this.pos];if(char==="{")return this.object();if(char==="[")return this.array();if(char==='"'||char==="'")return this.string();let tail=this.source.slice(this.pos);for(let[token,value]of[["true",!0],["false",!1],["null",null]])if(tail.startsWith(token)&&!/[A-Za-z0-9_$]/.test(tail[token.length]??""))return this.pos+=token.length,value;let number=tail.match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/);if(number)return this.pos+=number[0].length,Number(number[0]);throw Error("expected a data literal")}object(){this.pos++;let out={};if(this.ws(),this.take("}"))return out;for(;;){this.ws();let char=this.source[this.pos],key=char==='"'||char==="'"?this.string():this.identifier();if(key==="__proto__"||key==="prototype"||key==="constructor")throw Error("unsafe object key");if(this.ws(),!this.take(":"))throw Error("expected colon");if(out[key]=this.value(),this.ws(),this.take("}"))return out;if(!this.take(","))throw Error("expected comma");if(this.ws(),this.take("}"))return out}}array(){this.pos++;let out=[];if(this.ws(),this.take("]"))return out;for(;;){if(out.push(this.value()),this.ws(),this.take("]"))return out;if(!this.take(","))throw Error("expected comma");if(this.ws(),this.take("]"))return out}}identifier(){let match=this.source.slice(this.pos).match(/^[A-Za-z_$][A-Za-z0-9_$]*/);if(!match)throw Error("expected object key");return this.pos+=match[0].length,match[0]}string(){let quote=this.source[this.pos];if(quote!=='"'&"e!=="'")throw Error("expected string");this.pos++;let out="";while(this.pos<this.source.length){let char=this.source[this.pos++];if(char===quote)return out;if(char!=="\\"){out+=char;continue}let escaped=this.source[this.pos++];if(escaped===void 0)throw Error("unfinished escape");let simple={b:"\b",f:"\f",n:`
|
|
438
438
|
`,r:"\r",t:"\t","\\":"\\",'"':'"',"'":"'","/":"/"};if(escaped==="u"){let hex=this.source.slice(this.pos,this.pos+4);if(!/^[0-9a-fA-F]{4}$/.test(hex))throw Error("invalid unicode escape");out+=String.fromCharCode(Number.parseInt(hex,16)),this.pos+=4}else if(simple[escaped]!==void 0)out+=simple[escaped];else throw Error("invalid escape")}throw Error("unclosed string")}ws(){while(/\s/.test(this.source[this.pos]??""))this.pos++}take(value){if(!this.source.startsWith(value,this.pos))return!1;return this.pos+=value.length,!0}}class Parser{source;pos=0;constructor(source){this.source=source}parse(){if(this.skipWhitespace(),this.pos>=this.source.length)throw new ParseFailure("JSX is empty",this.pos);let root=this.element();if(this.skipWhitespace(),this.pos!==this.source.length)throw new ParseFailure("Expected a single root element",this.pos);return root}element(){let offset=this.pos;if(this.expect("<"),this.peek("/"))throw new ParseFailure("Unexpected closing tag",this.pos);let fragment=this.peek(">"),tag=fragment?null:this.name("tag name"),attributes=[];if(fragment)this.pos+=1;else while(!0){if(this.skipWhitespace(),this.peek("/>"))return this.pos+=2,{tag,attributes,children:[],offset};if(this.peek(">")){this.pos+=1;break}if(this.peek("{"))throw new ParseFailure("Spread attributes and JSX expressions are not supported",this.pos);let attrOffset=this.pos,name=this.name("attribute name");this.skipWhitespace();let value=!0;if(this.peek("="))this.pos+=1,this.skipWhitespace(),value=this.attributeValue();attributes.push({name,value,offset:attrOffset})}let children=[];while(!0){if(this.pos>=this.source.length)throw new ParseFailure(`Unclosed ${tag?`<${tag}>`:"fragment"}`,offset);if(this.peek("</")){if(this.pos+=2,tag===null)this.expect(">");else{let close=this.name("closing tag");if(close!==tag)throw new ParseFailure(`Expected </${tag}> but found </${close}>`,this.pos-close.length);this.skipWhitespace(),this.expect(">")}return{tag,attributes,children,offset}}if(this.peek("<")){children.push(this.element());continue}let textOffset=this.pos,next=this.source.indexOf("<",this.pos),end=next===-1?this.source.length:next,text=this.source.slice(this.pos,end),expression=text.indexOf("{");if(expression!==-1)throw new ParseFailure("JSX child expressions are not supported; use literal text or a JSON-valued prop",textOffset+expression);this.pos=end,children.push({text,offset:textOffset})}}attributeValue(){let quote=this.source[this.pos];if(quote==='"'||quote==="'"){let start=this.pos;this.pos+=1;let out="";while(this.pos<this.source.length){let char=this.source[this.pos];if(char===quote)return this.pos+=1,out;if(char==="\\"){let next=this.source[this.pos+1];if(next===void 0)break;out+=next==="n"?`
|
|
439
439
|
`:next==="t"?"\t":next,this.pos+=2}else out+=char,this.pos+=1}throw new ParseFailure("Unclosed quoted attribute",start)}if(!this.peek("{"))throw new ParseFailure("Attribute values must be quoted strings or JSON literals in braces",this.pos);let start=this.pos;this.pos+=1;let contentStart=this.pos,depth=1,quoteChar=null,escaped=!1;while(this.pos<this.source.length){let char=this.source[this.pos];if(quoteChar!==null){if(escaped)escaped=!1;else if(char==="\\")escaped=!0;else if(char===quoteChar)quoteChar=null}else if(char==='"'||char==="'")quoteChar=char;else if(char==="{")depth+=1;else if(char==="}"){if(depth-=1,depth===0){let raw=this.source.slice(contentStart,this.pos).trim();if(this.pos+=1,!raw)throw new ParseFailure("Empty JSX expression",start);try{return new DataLiteralParser(raw).parse()}catch{throw new ParseFailure("Brace values must be JSON literals (bare object keys, single quotes, and trailing commas are also allowed); identifiers as values, calls, template strings, spreads, and functions are not executed",contentStart)}}}this.pos+=1}throw new ParseFailure("Unclosed brace attribute",start)}name(label){let start=this.pos,first=this.source[this.pos];if(!first||!/[A-Za-z_$]/.test(first))throw new ParseFailure(`Expected ${label}`,this.pos);this.pos+=1;while(this.pos<this.source.length&&/[A-Za-z0-9_$-]/.test(this.source[this.pos]??""))this.pos+=1;return this.source.slice(start,this.pos)}skipWhitespace(){while(/\s/.test(this.source[this.pos]??""))this.pos+=1}peek(value){return this.source.startsWith(value,this.pos)}expect(value){if(!this.peek(value))throw new ParseFailure(`Expected ${value}`,this.pos);this.pos+=value.length}}function issueAt(source,offset,message){let lines=source.slice(0,offset).split(`
|
|
440
|
-
`);return{message,offset,line:lines.length,column:(lines.at(-1)?.length??0)+1}}function wrapMixedText(children,ctx){let hasElement=children.some((child)=>("tag"in child)),hasText=children.some((child)=>!("tag"in child)&&child.text.trim().length>0);if(!hasElement||!hasText||!ctx.components.has("Text"))return children;return children.flatMap((child)=>{if("tag"in child)return[child];if(child.text.trim().length===0)return[];return[{tag:"Text",attributes:[],children:[child],offset:child.offset}]})}function textValue(children){let elements=children.filter((child)=>("tag"in child)),text=children.filter((child)=>("text"in child)).map((child)=>child.text).join("").replace(/\s+/g," ").trim();return{...text?{text}:{},elements}}function snippetJsxTags(snippet){let pascal=(value)=>value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part)=>`${part[0]?.toUpperCase()??""}${part.slice(1)}`).join("");return[...new Set([snippet.id,snippet.name].map(pascal).filter(Boolean))]}function compileElement(element,ctx){if(element.tag===null){let meaningful=element.children.filter((child)=>("tag"in child)||child.text.trim().length>0);if(meaningful.length!==1||!("tag"in(meaningful[0]??{})))return{ok:!1,issues:[issueAt(ctx.source,element.offset,"A fragment must contain exactly one root element")]};return compileElement(meaningful[0],ctx)}let attrs=new Map;for(let attr of element.attributes){if(attrs.has(attr.name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Duplicate attribute "${attr.name}"`)]};if(/^on[A-Z]/.test(attr.name)||["dangerouslySetInnerHTML","ref","key"].includes(attr.name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Executable React attribute "${attr.name}" is not allowed in static design JSX`)]};attrs.set(attr.name,attr)}let stableId=attrs.get("vellooId")?.value;if(stableId!==void 0&&typeof stableId!=="string")return{ok:!1,issues:[issueAt(ctx.source,attrs.get("vellooId")?.offset??element.offset,"vellooId must be a string")]};attrs.delete("vellooId");let snippetMatches=ctx.snippets.get(element.tag)??[],isComponent=ctx.components.has(element.tag);if(isComponent&&snippetMatches.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`"${element.tag}" is ambiguous: both a component and a snippet use this tag`)]};let{text,elements}=textValue(wrapMixedText(element.children,ctx));if(text&&elements.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,"Mixed text and element children are not supported; wrap the text in a Text or Box element")]};if(isComponent){let props=Object.fromEntries([...attrs].map(([name,attr])=>[name,attr.value]));if(text){if("children"in props)return{ok:!1,issues:[issueAt(ctx.source,element.offset,"Specify children as text or a prop, not both")]};props.children=text}let children=[];for(let child of elements){let compiled=compileElement(child,ctx);if(!compiled.ok)return compiled;children.push(compiled.node)}return{ok:!0,node:{$ref:element.tag,...stableId?{$id:stableId}:{},...Object.keys(props).length>0?{props}:{},...children.length>0?{children}:{}}}}if(snippetMatches.length===1){let snippet=snippetMatches[0],params=new Map(snippet.params.map((param)=>[param.name,param])),args={},extraClassName;for(let[name,attr]of attrs)if(name==="className"&&!params.has(name)){if(typeof attr.value!=="string")return{ok:!1,issues:[issueAt(ctx.source,attr.offset,"A snippet className must be a string")]};extraClassName=attr.value}else if(!params.has(name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Unknown prop "${name}" for snippet ${element.tag}`)]};else args[name]=attr.value;if(text||elements.length>0){let childParam=params.get("children");if(!childParam)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} declares no children param`)]};if(text)args.children=text;else{if(childParam.type!=="node"||elements.length!==1)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} needs one node child for its children param`)]};let compiled=compileElement(elements[0],ctx);if(!compiled.ok)return compiled;args.children=compiled.node}}let missing=snippet.params.filter((param)=>!param.optional&¶m.default===void 0&&!(param.name in args)).map((param)=>param.name);if(missing.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} is missing required props: ${missing.join(", ")}`)]};return{ok:!0,node:{$snippet:snippet.id,...stableId?{$id:stableId}:{},...extraClassName?{$extraClassName:extraClassName}:{},...Object.keys(args).length>0?{args}:{}}}}let allNames=[...ctx.components,...ctx.snippets.keys()],suggestions=nearestRefs(element.tag,allNames),catalogOnly=ctx.catalog.has(element.tag);return{ok:!1,issues:[issueAt(ctx.source,element.offset,catalogOnly?`Component "${element.tag}" is known to the library but unavailable in its design renderer; refresh the provider snapshot`:`Unknown component or snippet "${element.tag}"${suggestions.length?`; did you mean ${suggestions.join(", ")}?`:""}`)]}}async function compileRestrictedJsx(ctx,screen,source){let root;try{root=new Parser(source).parse()}catch(error){if(error instanceof ParseFailure)return{ok:!1,issues:[issueAt(source,error.offset,error.message)]};throw error}let registry=registryForScreen2(ctx,screen),manifest=await providerForScreen2(ctx,screen).loadManifest().catch(()=>[]),snippets=new Map;for(let snippet of ctx.folder.snippets.values())for(let tag of snippetJsxTags(snippet)){let values=snippets.get(tag)??[];values.push(snippet),snippets.set(tag,values)}return compileElement(root,{source,components:new Set(Object.keys(registry)),catalog:new Set(manifest.map((descriptor)=>descriptor.id)),snippets})}var ViewportArgSchema=preprocess((v)=>{if(typeof v!=="object"||v===null||Array.isArray(v))return v;let o=v;if(o.w!==void 0||o.h!==void 0)return v;let{width,height,...rest}=o;return width===void 0&&height===void 0?v:{...rest,w:width,h:height}},ViewportSchema),ThemeNameSchema=string().optional().describe("Named theme to render with (boards pin one)"),RenderModeSchema=_enum(["light","dark","compare"]).optional();function sourceError(message,issues){return errorResult({kind:"BadRequest",message,issues})}function registerComposeTool(mcp,ctx,jit){mcp.registerTool("compose",{description:"Append one subtree or replace a screen tree using safe restricted JSX. Tags resolve automatically across the screen's library, extensions, and PascalCase snippet names. Supports nested tags, literal text, quoted props, and JSON literals in braces; no JavaScript executes. Use `vellooId` for a stable @id. Errors include line/column. Missing host-app packages never block design and are reported later by `emit_code.componentsToInstall`.",inputSchema:{screenId:string(),mode:_enum(["append","replace"]),jsx:string().min(1),parentPath:LocatorSchema.optional().describe("append only; default [] (the root node)"),index:number().int().nonnegative().optional().describe("append only")}},async({screenId,mode,jsx,parentPath,index})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult({kind:"ScreenNotFound",screenId});if(mode==="replace"&&(parentPath!==void 0||index!==void 0))return sourceError("compose replace does not accept parentPath or index",[{message:"Remove append-only arguments",offset:0,line:1,column:1}]);let compiled=await compileRestrictedJsx(ctx,screen,jsx);if(!compiled.ok)return sourceError("compose could not compile the restricted JSX",compiled.issues);let mutation;if(mode==="replace")mutation=await setScreenTree2(ctx,{screenId,tree:compiled.node});else if(isComponentNode(compiled.node))mutation=await addNode2(ctx,{screenId,parentPath:parentPath??[],componentRef:compiled.node.$ref,...compiled.node.$id?{id:compiled.node.$id}:{},...compiled.node.props?{props:compiled.node.props}:{},...compiled.node.children?{children:compiled.node.children}:{},...index!==void 0?{index}:{}});else if(isSnippetInstance(compiled.node))mutation=await instantiateSnippet2(ctx,{screenId,parentPath:parentPath??[],snippetId:compiled.node.$snippet,...compiled.node.$id?{id:compiled.node.$id}:{},...compiled.node.args?{args:compiled.node.args}:{},...compiled.node.$extraClassName?{extraClassName:compiled.node.$extraClassName}:{},...index!==void 0?{index}:{}});else return sourceError("compose root must be a component or snippet",[{message:"Parameter references are only valid inside snippet definitions",offset:0,line:1,column:1}]);if(!mutation.ok)return toMcp(mutation);let resulting=ctx.folder.screens.get(screenId),[propWarnings,diagnostics]=await Promise.all([resulting&&isComponentNode(compiled.node)?propWarningsForTree(ctx,resulting,compiled.node).catch(()=>[]):[],resulting?diagnosticsForScreen(ctx,jit,resulting).catch(()=>[]):[]]);return jsonResult({mode,...mutation.value,root:isComponentNode(compiled.node)?{kind:"component",id:compiled.node.$ref}:isSnippetInstance(compiled.node)?{kind:"snippet",id:compiled.node.$snippet}:{kind:"unknown"},...propWarnings.length>0?{propWarnings}:{},...diagnostics.length>0?{diagnostics}:{}})})}import{readFile as readFile13}from"fs/promises";import{join as join31}from"path";var FoundNodeSchema=looseObject({path:array(number()),kind:_enum(["component","snippet","param"]),ref:string().optional(),id:string().optional(),className:string().optional(),textPreview:string().optional(),childCount:number()}),FindNodesOutput=looseObject({matches:array(FoundNodeSchema),total:number()}),ListComponentsOutput=looseObject({snapshotVersion:string(),components:array(looseObject({id:string(),kind:_enum(["library","extension","snippet"]),availableInDesign:boolean(),installedInApp:boolean(),snippetId:string().optional(),unused:boolean().optional(),importPath:string().optional()}))}),EmitSnippetIrSchema=looseObject({id:string(),componentName:string(),params:array(looseObject({name:string(),type:string(),default:string().optional(),optional:boolean().optional()})),jsx:string(),componentsToInstall:array(string()),helpersToMaterialize:array(string()),warnings:array(string())}),EmitCodeOutput=looseObject({screen:looseObject({id:string(),name:string()}),jsx:string(),componentsUsed:array(string()),iconsUsed:array(string()),snippetsUsed:array(EmitSnippetIrSchema),classesUsed:array(string()),componentsToInstall:array(string()),helpersToMaterialize:array(string()),warnings:array(string()),tailwindV3Compat:array(unknown()).optional()}),CompareToUrlOutput=looseObject({similarity:number(),changedRatio:number(),contentSimilarity:number().optional(),heightDelta:number(),bitmapHeightDelta:number().optional(),contentHeight:number(),topMismatches:array(string()).optional(),styleDiff:array(unknown()).optional(),regions:array(unknown()),framesShorterThanContent:array(unknown()).optional(),note:string().optional(),capture:looseObject({}).optional(),urlCache:looseObject({}).optional(),unverified:literal(!0).optional(),redirected:looseObject({}).optional(),authWall:literal(!0).optional(),pageError:string().optional(),warning:string().optional()});function withRequiredFlag(param){return{...param,required:param.default===void 0&&!param.optional}}function shortClass(cls,max=40){let trimmed=cls.trim();return trimmed.length>max?`${trimmed.slice(0,max-1)}\u2026`:trimmed}var MAX_ENUM_VALUES=40;function trimLargeEnums(entry){if(!entry.props.some((p)=>p.enumValues&&p.enumValues.length>MAX_ENUM_VALUES))return entry;let props=entry.props.map((p)=>p.enumValues&&p.enumValues.length>MAX_ENUM_VALUES?{...p,enumValues:p.enumValues.slice(0,MAX_ENUM_VALUES),enumValuesTruncated:p.enumValues.length-MAX_ENUM_VALUES}:p);return{...entry,props}}function nodeToOutline(node){if(isParamRef(node))return{param:node.$param};if(isSnippetInstance(node)){let out={snippet:node.$snippet},id=nodeId(node);if(id)out.$id=id;return out}if(!isComponentNode(node))return{};let out={ref:node.$ref},id=nodeId(node);if(id)out.$id=id;let cls=typeof node.props?.className==="string"?node.props.className:"";if(cls)out.classSnippet=shortClass(cls);if(node.children&&node.children.length>0)out.children=node.children.map(nodeToOutline);return out}function toOutline(screen){return{id:screen.id,name:screen.name,tree:nodeToOutline(screen.tree)}}async function loadManifestForCtx2(ctx){let onDisk=join31(ctx.folder.root,".design","manifest.json");try{let raw=await readFile13(onDisk,"utf8");return JSON.parse(raw)}catch{return ctx.defaultProvider.loadManifest()}}function toSummary(c){let out={id:c.id,category:c.category,source:c.source,props:c.props.map((p)=>p.name)};if(c.designModeNotes)out.designModeNotes=c.designModeNotes;return out}var EXTENSION_SHELF={id:"extensions",label:"Extensions (this app's own)"},SNIPPET_SHELF={id:"snippets",label:"Snippets (your compositions)"};function componentIndex(entries,groupOrder,labelFor){let byGroup=new Map;for(let entry of entries){let group=entry.group??"",familyId=entry.family??entry.id,families=byGroup.get(group)??new Map,family=families.get(familyId)??{id:familyId};if(entry.id!==familyId)family.pieces=[...family.pieces??[],entry.id];if(entry.designModeNotes)family.designModeNotes=entry.id===familyId?[entry.designModeNotes,family.designModeNotes].filter(Boolean).join(" "):[family.designModeNotes,`${entry.id}: ${entry.designModeNotes}`].filter(Boolean).join(" ");families.set(familyId,family),byGroup.set(group,families)}let order=[...groupOrder.map((g)=>g.id),""],groups=[...byGroup.entries()].sort(([a],[b])=>order.indexOf(a)-order.indexOf(b)).map(([group,families])=>({group:group===""?"other":group,label:labelFor(group===""?void 0:group),families:[...families.values()].map((family)=>family.pieces?{...family,pieces:family.pieces.sort()}:family).sort((a,b)=>a.id.localeCompare(b.id))})),index={groups,totals:{components:entries.length,families:groups.reduce((sum,g)=>sum+g.families.length,0)}},unavailable=entries.filter((e)=>!e.availableInDesign).map((e)=>e.id),uninstalled=entries.filter((e)=>!e.installedInApp).map((e)=>e.id);if(unavailable.length>0)index.unavailableInDesign=unavailable;if(uninstalled.length>0)index.notInstalledInApp=uninstalled;return index}function listBoardsPayload(folder,opts={}){let entries=opts.includeArchived?orderedBoards(folder):activeBoards(folder),groups=new Map((folder.config.boardGroups??[]).map((g)=>[g.id,g.name]));return entries.map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,...board.group?{group:groups.get(board.group)??board.group}:{},...board.archivedAt?{archivedAt:board.archivedAt}:{},...opts.includeFrames?{frames:board.frames,groups:board.groups}:{}}))}function registerDiscoveryTools(mcp,ctx){mcp.registerTool("list_screens",{description:"List every screen in the design folder. Pass `includeTree: true` to embed each screen's full tree \u2014 one round-trip instead of list_screens + N get_screen calls. Default false to keep responses small.",inputSchema:{includeTree:boolean().optional()}},async({includeTree})=>{let screens=[...ctx.folder.screens.entries()].map(([id,screen])=>({id,name:screen.name,...includeTree?{tree:screen.tree}:{}}));return jsonResult({screens})}),mcp.registerTool("get_screen",{description:'Return one screen\'s JSON. `mode: "full"` (default) returns the complete tree; `"outline"` returns a stripped tree per node \u2014 {ref|snippet, $id?, classSnippet, children} \u2014 for an overview of a large screen before drilling in.',inputSchema:{screenId:string(),mode:_enum(["full","outline"]).optional()}},async({screenId,mode})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(screenNotFound(screenId));if(mode==="outline")return jsonResult(toOutline(screen));return jsonResult(screen)}),mcp.registerTool("list_boards",{description:"List the folder's live boards in sidebar order, each with its own frames. `group` names the sidebar group a board is filed under (absent \u21D2 ungrouped). `includeFrames: true` embeds each board's frame list; `includeArchived: true` also lists archived boards, which carry `archivedAt`.",inputSchema:{includeFrames:boolean().optional(),includeArchived:boolean().optional()}},async({includeFrames,includeArchived})=>jsonResult({boards:listBoardsPayload(ctx.folder,{includeFrames,includeArchived})})),mcp.registerTool("get_board",{description:"Return one board's full JSON \u2014 frames (placements of screens at chosen sizes) and groups.",inputSchema:{boardId:string()}},async({boardId})=>{let board=ctx.folder.boards.get(boardId);if(!board)return errorResult(boardNotFound(boardId));return jsonResult(board)}),mcp.registerTool("list_components",{description:'List the unified `compose` tag namespace: library components, extensions, and snippets. `mode: "index"` (default) groups families onto shelves with their sub-pieces folded in and carries per-family usage notes \u2014 read this first, then `filter` to the handful you will actually use. `"summary"` adds prop names per component; `"full"` returns whole descriptors with examples. A family entry means you compose its `pieces` inside it (`Field` \u21D2 `FieldLabel`/`FieldDescription`/`FieldError`) \u2014 prefer a real family over rebuilding one from `Box` and `Text`. Snippet entries carry `snippetId`; their `id` is the PascalCase JSX tag. `installedInApp` is host-app status only; false does not block design, and `emit_code.componentsToInstall` carries the handoff plan. `filter` substring-matches tags; `kind` narrows the result. A snippet no screen reaches \u2014 directly or through another snippet \u2014 is marked `unused`; `unusedOnly: true` lists just those, which is how you find snippets a board delete or a screen rewrite left behind.',outputSchema:ListComponentsOutput,inputSchema:{filter:string().optional(),mode:_enum(["index","summary","full"]).optional(),kind:_enum(["library","extension","snippet"]).optional(),unusedOnly:boolean().optional().describe('Only snippets nothing reaches \u2014 implies kind: "snippet"')}},async({filter,mode,kind,unusedOnly})=>{let manifest=await loadManifestForCtx2(ctx),provider=ctx.defaultProvider,catalog=provider.catalog?await provider.catalog().catch(()=>[]):[],catalogById=new Map(catalog.map((entry)=>[entry.id,entry])),libraryEntries=manifest.map((c)=>{let status=catalogById.get(c.id);return{...c,kind:"library",availableInDesign:c.id in provider.registry,...status?{installedInApp:status.installed,importPath:status.importPath}:{installedInApp:!0}}}),extensions=ctx.folder.config.extensions??{},extensionEntries=Object.entries(extensions).map(([id,ext])=>({id,category:ext.category??"ui",source:"extension",props:ext.props,designModeNotes:ext.description,kind:"extension",importPath:ext.importPath,availableInDesign:!0,installedInApp:!0})),unused=new Set(unusedSnippetIds(ctx.folder)),snippetEntries=[...ctx.folder.snippets.values()].map((snippet)=>({id:snippetJsxTags(snippet)[0]??snippet.id,snippetId:snippet.id,category:"composition",source:"snippet",props:snippet.params.map(withRequiredFlag),designModeNotes:unused.has(snippet.id)?`Reusable snippet: ${snippet.name} \u2014 unused: no screen reaches it`:`Reusable snippet: ${snippet.name}`,kind:"snippet",availableInDesign:!0,installedInApp:!0,...unused.has(snippet.id)?{unused:!0}:{}})),all=unusedOnly?snippetEntries.filter((entry)=>unused.has(entry.snippetId)):kind==="library"?libraryEntries:kind==="extension"?extensionEntries:kind==="snippet"?snippetEntries:[...libraryEntries,...extensionEntries,...snippetEntries],filtered=filter?all.filter((c)=>c.id.toLowerCase().includes(filter.toLowerCase())):all;if((mode??"index")==="index"){let order=[...COMPONENT_GROUPS,EXTENSION_SHELF,SNIPPET_SHELF],shelfOf=(group)=>order.find((g)=>g.id===group)?.label??UNGROUPED_LABEL,index=componentIndex(filtered.map((c)=>({id:c.id,group:c.kind==="extension"?EXTENSION_SHELF.id:c.kind==="snippet"?SNIPPET_SHELF.id:("group"in c)?c.group:void 0,family:"family"in c?c.family:void 0,designModeNotes:c.designModeNotes,availableInDesign:c.availableInDesign,installedInApp:c.installedInApp})),order,shelfOf);return structuredResult({snapshotVersion:ctx.defaultProvider.version,...index})}let out=mode==="full"?filtered.map((entry)=>entry.kind==="snippet"?entry:trimLargeEnums(entry)):filtered.map((c)=>({...c.kind==="snippet"?{id:c.id,category:c.category,source:c.source,props:c.props.map((prop)=>prop.name),designModeNotes:c.designModeNotes}:toSummary(c),kind:c.kind,availableInDesign:c.availableInDesign,installedInApp:c.installedInApp,...c.kind==="snippet"?{snippetId:c.snippetId,..."unused"in c?{unused:c.unused}:{}}:{},..."importPath"in c?{importPath:c.importPath}:{}}));return structuredResult({snapshotVersion:ctx.defaultProvider.version,components:out})}),mcp.registerTool("component_status",{description:'Report how components render: exact host source, canvas-adapted source, a real bundled framework adapter, fallback, or unavailable. Pass `screen` to check exactly the components a screen uses and whether it client-mounts host source. Or pass `ids` (e.g. { ids: ["Button", "Card"] }) \u2014 the same ids `list_components` returns. `renderable` describes whether Velloo can render it; `hostMount` is the separate host-app browser-mount capability.',inputSchema:{ids:array(string().min(1)).min(1).optional(),screen:string().min(1).optional().describe("Screen id: report the components it uses and whether it mounts"),library:string().min(1).optional().describe("With `ids`; a screen names its own")}},async({ids,screen:screenId,library})=>{if(screenId!==void 0){let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(screenNotFound(screenId));if(!ctx.canvasBundler)return errorResult({kind:"BadRequest",message:"component_status { screen } needs the canvas daemon's bundler."});let mount=await screenMount(ctx,ctx.canvasBundler,screen);if(mount.kind==="none"){let provider=providerForScreen2(ctx,screen),libraryId=libraryIdForScreen(ctx,screen);if(!provider.canvasBundleSpec){let manifest=await provider.loadManifest().catch(()=>[]),known=new Set(manifest.map((entry)=>entry.id)),refs=collectSerializedRefs(serializeTree(screen.tree,{snippets:ctx.folder.snippets})).filter((id)=>known.has(id));return jsonResult({library:libraryId,screen:screenId,renderable:!0,renderSource:"bundled-adapter",hostMount:{supported:!1,mounted:!1},note:"This screen renders through Velloo's bundled framework adapter using the real library runtime. Host-app client mounting is not supported by this adapter; that does not make its components unusable.",diagnostics:refs.map((id)=>({id,status:"bundled",note:"Rendered by the real library through Velloo's bundled adapter."})),errors:[]})}return jsonResult({screen:screenId,mounted:!1,note:"This screen's library renders server-side only, or the screen uses no components \u2014 there is no app mount to report on.",diagnostics:[],errors:[]})}return jsonResult({library:mount.libraryId,screen:screenId,mounted:mount.kind==="mounted",...mount.kind==="server"?{note:`The canvas and every capture render this screen from Velloo's bundled components, not the app's own, because ${mount.reason}. Statuses below describe each component's source; none of them reaches the screen until the blocking ones are fixed or replaced.`}:{},diagnostics:mount.bundle?.diagnostics??[],errors:mount.bundle?.errors??[]})}if(!ids)return errorResult({kind:"BadRequest",message:'Pass `screen` (a screen id) or `ids` (component ids), e.g. { screen: "home" }.'});let libraryId=library??ctx.folder.config.defaultLibrary,provider=ctx.providers[libraryId];if(!provider)return errorResult({kind:"BadRequest",message:`Unknown library ${JSON.stringify(libraryId)}.`});let manifest=await provider.loadManifest().catch(()=>[]),known=new Set(manifest.map((entry)=>entry.id)),unknownDiagnostics=ids.filter((id)=>!known.has(id)).map((id)=>({id,status:"unknown",note:"Not a component in this library \u2014 call list_components for the ids it accepts. An app component under its own name is not one of them; design with the library's components and match the app's styling."})),recognized=ids.filter((id)=>known.has(id));if(!provider.canvasBundleSpec||!ctx.canvasBundler)return jsonResult({library:libraryId,renderable:!0,renderSource:"bundled-adapter",hostMount:{supported:!1,mounted:!1},diagnostics:[...recognized.map((id)=>({id,status:"bundled",note:"Rendered by the real library through Velloo's bundled adapter; host-app client mounting is unavailable."})),...unknownDiagnostics],errors:[]});let result=recognized.length?await ctx.canvasBundler.build(libraryId,recognized):{usable:!1,diagnostics:[],errors:[]};return jsonResult({library:libraryId,usable:result.usable,diagnostics:[...result.diagnostics,...unknownDiagnostics],errors:result.errors})}),mcp.registerTool("get_theme",{description:"Return a theme token tree \u2014 the default, or a named one via `theme` \u2014 plus the folder's `customCss`. `typography.typesets` holds the rhythm controls and `typeScale` shows what they compute to per role. Adjust via `set_theme`, not per-node sizes.",inputSchema:{theme:string().optional().describe('Named theme to read; default "default"')}},async(args)=>{let resolved=resolveNamedTheme(ctx.folder,args.theme);if(!resolved.ok)return errorResult({kind:"BadRequest",message:resolved.message});let typography=resolved.theme.typography;return jsonResult({...resolved.theme,typeScale:typesetScale(typography.typesets?.[DEFAULT_TYPESET_NAME],{...typography.fontFamily?{fontFamily:typography.fontFamily}:{}}),customCss:ctx.folder.customCss})}),mcp.registerTool("get_snippet",{description:"Return the full JSON for a single snippet (id, name, params, body tree). Each param reports a derived `required` flag (no default and not optional).",inputSchema:{snippetId:string()}},async({snippetId})=>{let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return errorResult(snippetNotFound(snippetId));return jsonResult({...snippet,params:snippet.params.map(withRequiredFlag)})}),mcp.registerTool("list_annotations",{description:"List a screen's annotations \u2014 designer-authored guidance plus any you pinned. Each is anchored to a node and carries an `author`; `resolved` is the live path, or null when the targeted node has vanished (treat those as low-priority). User-authored ones are read-only to you. Bodies are markdown.",inputSchema:{screenId:string()}},async({screenId})=>{let screen=ctx.folder.screens.get(screenId),list=(ctx.folder.annotations.get(screenId)??[]).map((a)=>{let resolved=screen?resolveLocator(screen.tree,a.target.locator):null;return{...a,resolved}});return jsonResult({annotations:list})}),mcp.registerTool("list_notes",{description:"List the markdown notes on one board \u2014 commentary beside the frames or attached to a node (tour steps, review remarks, handoff context). An attached note carries `attachment` naming the frame, screen and node it anchors to.",inputSchema:{boardId:string()}},async({boardId})=>{let notes=ctx.folder.notes.get(boardId)??[];return jsonResult({notes})})}import{isAbsolute as isAbsolute4,resolve as resolve9,sep}from"path";function v3CompatFor(ctx,classes){let hostRoot=hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp);if(detectTailwindMajor(hostRoot)!==3)return[];return v3ClassIssues(classes)}async function targetFor(ctx,thing){let provider=providerForScreen2(ctx,thing);if(!provider.codegenModule)return;let manifest=await provider.loadManifest();return moduleTarget(manifest.filter((c)=>c.source!=="velloo").map((c)=>c.id),provider.codegenModule)}function isInlineStyle(ctx,thing){return styleChannelOf(providerForScreen2(ctx,thing),ctx.folder.config.styling?.framework).kind==="style"}function registerEmitTools(mcp,ctx,jit){mcp.registerTool("emit_code",{description:"Return agent-consumed IR for a screen plus full class/theme diagnostics: the JSX body in the screen framework's native idiom (Tailwind classes for shadcn, `sx={{\u2026}}` for MUI), plus the components, icons, snippets and classes used. **Not** a paste-ready file \u2014 no imports, no prettier pass. Read it and write the real code in the user's app conventions.",outputSchema:EmitCodeOutput,inputSchema:{screenId:string(),componentsAlias:string().optional()}},async(args)=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return errorResult(screenNotFound(args.screenId));let componentsAlias=args.componentsAlias??ctx.folder.config.codegen?.componentsAlias,target=await targetFor(ctx,screen),inlineStyle=isInlineStyle(ctx,screen),result=await emitCode(screen,{...componentsAlias?{componentsAlias}:{},snippets:ctx.folder.snippets,extensions:ctx.folder.config.extensions,...target?{target}:{},...inlineStyle?{inlineStyle:!0}:{}});if(!result.ok)return errorResult(result.error);let compat=target||inlineStyle?[]:v3CompatFor(ctx,[...result.value.classesUsed,...result.value.snippetsUsed.flatMap((s)=>classNamesInJsx(s.jsx))]),diagnostics=await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]);return structuredResult({...result.value,...compat.length>0?{tailwindV3Compat:compat}:{},...diagnostics.length>0?{diagnostics}:{}})}),mcp.registerTool("emit_snippet",{description:"Return agent-consumed IR for a single snippet: PascalCase component name, typed params, JSX body.",inputSchema:{snippetId:string(),componentsAlias:string().optional()}},async(args)=>{let snippet=ctx.folder.snippets.get(args.snippetId);if(!snippet)return errorResult(snippetNotFound(args.snippetId));let componentsAlias=args.componentsAlias??ctx.folder.config.codegen?.componentsAlias,target=await targetFor(ctx,snippet),inlineStyle=isInlineStyle(ctx,snippet),result=await emitSnippet(snippet,{...componentsAlias?{componentsAlias}:{},snippets:ctx.folder.snippets,extensions:ctx.folder.config.extensions,...target?{target}:{},...inlineStyle?{inlineStyle:!0}:{}});if(!result.ok)return errorResult(result.error);let compat=target||inlineStyle?[]:v3CompatFor(ctx,classNamesInJsx(result.value.jsx)),diagnostics=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);return structuredResult({...result.value,...compat.length>0?{tailwindV3Compat:compat}:{},...diagnostics.length>0?{diagnostics}:{}})}),mcp.registerTool("emit_theme",{description:"Write the active framework's theme artifact \u2014 shadcn \u21D2 Tailwind globals.css, native frameworks \u21D2 their own theme module \u2014 plus a framework-neutral DTCG `tokens.json`. Dry-run by default. These are finished artifacts, not IR: no agent translation, and the result's `notes` carry any one-time wiring steps. Guide: velloo://guide/theme.",inputSchema:{outputDir:string(),cssPath:string().optional().describe('shadcn: globals.css location relative to outputDir; default "app/globals.css"'),themePath:string().optional().describe('MUI: createTheme module location relative to outputDir; default "theme.ts"'),apply:boolean().optional(),cssOnly:boolean().optional(),theme:string().optional().describe("Named theme to emit; default 'default'"),tailwind:union([literal(3),literal(4)]).optional().describe("Force the Tailwind major of the emitted artifacts; default: detected from outputDir's package.json")}},async(args)=>{let out=resolve9(ctx.folder.root,args.outputDir);for(let rel of[args.cssPath,args.themePath]){if(rel===void 0)continue;if(isAbsolute4(rel)||!resolve9(out,rel).startsWith(out+sep))return errorResult(`emit_theme: path must stay within outputDir (got ${rel})`)}let theme=themeByName(ctx.folder,args.theme),adapter=ctx.defaultProvider;if(adapter.themeToNative&&adapter.themeModule){let result=await emitNativeTheme(adapter.themeToNative(theme),{spec:adapter.themeModule,outputDir:out,...args.themePath?{themePath:args.themePath}:{},...theme.colorsDark?{darkThemeOptions:adapter.themeToNative(theme,!0)}:{},sourceTheme:theme,apply:args.apply??!1});return jsonResult({files:result.files})}let tailwindMajor=args.tailwind??detectTailwindMajor(out),result=await emitTheme(theme,{outputDir:out,...args.cssPath?{cssPath:args.cssPath}:{},apply:args.apply??!1,cssOnly:args.cssOnly,customCss:ctx.folder.customCss,...tailwindMajor===3?{tailwindMajor:3}:{}});return jsonResult({files:result.files,...result.warnings.length>0?{warnings:result.warnings}:{},...result.notes.length>0?{notes:result.notes}:{}})})}var PropControlSchema=_enum(["boolean","number","string","color","enum","icon"]),ExtensionPropArg=object({name:string().min(1),type:string().min(1),optional:boolean(),control:PropControlSchema,defaultValue:string().optional(),enumValues:array(union([string(),number()])).optional()});function registerExtensionTools(mcp,ctx){mcp.registerTool("add_extension",{description:'Register one of the app\'s own components \u2014 a bespoke DataTable, a brand Hero, a custom Chart \u2014 so screens can use it. The canvas draws a placeholder and `emit_code` writes a real import from `importPath`; `render: "live"` bundles and mounts the actual component instead (charts above all). For compositions of components that already exist, use `add_snippet`. Guide: velloo://guide/extensions.',inputSchema:{id:string().min(1),importPath:string().min(1),props:array(ExtensionPropArg),category:_enum(["ui","typography"]).optional(),description:string().optional(),render:_enum(["static","live"]).optional().describe('"live" client-mounts the real component (charts); default "static" placeholder'),app:string().min(1).optional().describe(`monorepos only: which host app the component lives in \u2014 a config.hostApps key (e.g. "web", "admin"). The live island bundles from that app's root + node_modules. Omit for the default host app.`),fit:_enum(["aspect-video","content"]).optional().describe('live-island sizing: "aspect-video" (default) locks a 16:9 box; "content" lets a fixed-height chart (e.g. ResponsiveContainer height={300}) or an absolute-inset overlay drive its own height')}},async(args)=>toMcp(await addExtension(ctx,args))),mcp.registerTool("update_extension",{description:"Patch an existing extension \u2014 add or remove props, fix `importPath` after a refactor, update the description. Sparse: unlisted fields keep their values. **To rename**, remove and re-add instead; renaming would break every existing tree reference.",inputSchema:{id:string().min(1),patch:object({importPath:string().min(1).optional(),props:array(ExtensionPropArg).optional(),category:_enum(["ui","typography"]).optional(),description:string().optional(),render:_enum(["static","live"]).optional(),app:string().min(1).optional(),fit:_enum(["aspect-video","content"]).optional()})}},async(args)=>toMcp(await updateExtension(ctx,args))),mcp.registerTool("remove_extension",{description:"Unregister an extension. Refuses while any screen or snippet tree still references it, returning the offending nodes so you can remove them or swap their `$ref` first, then call again.",inputSchema:{id:string().min(1)}},async(args)=>toMcp(await removeExtension(ctx,args)))}var MAX_SENDS_PER_SESSION=20;function registerFeedbackTool(mcp,ctx,cloud){let sent=0;mcp.registerTool("send_feedback",{description:"Send free-text feedback about **Velloo itself** \u2014 a confusing instruction, a missing capability, a tool that misbehaved. NOT for feedback about the user's design. Show the user the exact `body` and get their go-ahead first; never send unprompted, and never include their design content, code, or file paths. Sent anonymously unless they opted into contact \u2014 a blind-signed token replaces the account credential \u2014 but needs a signed-in session either way. On failure the error's `reason` says whether to ask the user to sign in, retry later, or just carry on.",inputSchema:{body:string().min(1).max(6000).describe("The feedback in plain prose. No design content, code, or file/repo paths.")}},async({body})=>{if(sent>=MAX_SENDS_PER_SESSION)return errorResult(feedbackError("session-limit",`Feedback limit reached for this session (${MAX_SENDS_PER_SESSION}). Don't retry; carry on with the task.`,!1));let token=await currentToken(cloud);if(!token)return errorResult(feedbackError("signed-out","Not signed in to velloo-cloud, so feedback can't be sent. Tell the user they can run `velloo login` if they want to send it, then carry on \u2014 don't retry without that.",!1));if(!ctx.folder.config.feedback?.contactOk){let result=await sendAnonymousFeedback(cloud,{body,toolVersion:ctx.folder.config.toolVersion,source:"agent"});if(!result.ok)return errorResult(result.error);return sent+=1,jsonResult({message:result.value})}try{let res=await fetch(`${cloud.url}/v1/feedback`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${token}`},body:JSON.stringify({body,source:"agent",toolVersion:ctx.folder.config.toolVersion,contactOk:ctx.folder.config.feedback?.contactOk??!1}),signal:AbortSignal.timeout(5000)});if(!res.ok)return errorResult(feedbackError("rejected",`velloo-cloud refused the message (${res.status}).`,!1));return sent+=1,jsonResult({message:"Thanks \u2014 your feedback was sent."})}catch{return errorResult(feedbackError("unreachable","Couldn't reach velloo-cloud; feedback not sent. Carry on with the task.",!0))}})}function registerGenerateTools(mcp,ctx,cloud){mcp.registerTool("generate_asset",{description:"Generate image assets from a prompt into assets/. **PAY-AS-YOU-GO against the user's credit balance** \u2014 author it yourself with `upload_asset` when you can, check `list_assets` first, iterate at `count: 1`, and ALWAYS relay the returned cost and remaining balance to the user. `intent` picks the model; `reference` restyles or edits an existing asset. Guide: velloo://guide/art.",inputSchema:{prompt:string().min(1).max(2000).describe("What to generate, in plain language. Describe subject, style, lighting and mood \u2014 these models reward detail."),intent:_enum(INTENTS).describe("What the artwork is for; picks the model. See above."),aspect:_enum(ASPECTS).optional().describe('Shape of the output. Defaults per intent ("16:9" for photo/graphic/texture, "1:1" for illustration/icon/vector). Ignored by edit/cutout/upscale, which follow the source.'),count:number().int().min(1).max(4).optional().describe("Variants to generate in one call (default 1, max 4). EACH ONE IS CHARGED. Use 2-4 to give the user a choice on a hero asset; use 1 while iterating on a prompt."),reference:array(string()).max(3).optional().describe('Existing assets to work from, as folder paths ("assets/hero.png"). REQUIRED by edit/cutout/upscale. Optional for photo/illustration/graphic, where it guides style or subject \u2014 e.g. "a stylized version of this landscape".'),filename:string().optional().describe("Stem for assets/<filename>.<png|svg>; default: the generation id. With count > 1 the variants become <filename>-1, <filename>-2, \u2026")}},async({prompt,intent,aspect,count,reference,filename})=>{let r=await generateAsset(ctx.folder.root,cloud,{prompt,intent,aspect,count,reference,filename});if(!r.ok)return errorResult(`generate_asset: ${describeGenerateFailure(r.error)}`);return jsonResult(r.value)})}import{readFileSync as readFileSync7}from"fs";import{join as join32}from"path";function measure(node){return{path:node.nodePath??"",tag:node.tag,...node.class?{class:node.class}:{},rect:node.rect,style:node.style}}function measuredAt(dom,path){let key=path.join("."),found=dom.nodes.find((n)=>n.nodePath===key);return found?measure(found):null}function childrenMeasuredAt(dom,path,limit=12){let prefix=path.length===0?"":`${path.join(".")}.`,out=[];for(let node of dom.nodes){let p=node.nodePath;if(p===void 0||!p.startsWith(prefix)||p===prefix.slice(0,-1))continue;if(p.slice(prefix.length).includes("."))continue;if(out.push(measure(node)),out.length>=limit)break}return out}function overlap(a,b){let ix=Math.max(0,Math.min(a.x+a.w,b.x+b.w)-Math.max(a.x,b.x)),iy=Math.max(0,Math.min(a.y+a.h,b.y+b.h)-Math.max(a.y,b.y)),area=a.w*a.h;return area>0?ix*iy/area:0}function counterpartOf(reference,rect){let best=null,bestScore=0;for(let node of reference.nodes){let score=Math.min(overlap(rect,node.rect),overlap(node.rect,rect));if(score>bestScore)bestScore=score,best=node}return best&&bestScore>=0.6?measure(best):null}function styleDifferences(design,page,limit=8){let out=[];for(let property of new Set([...Object.keys(design.style),...Object.keys(page.style)])){let a=design.style[property]??"",b=page.style[property]??"";if(a!==b)out.push({property,design:a,page:b});if(out.length>=limit)break}return out}function readCaptureDom(folderRoot,captureId,folderId){try{return JSON.parse(readFileSync7(join32(captureDir(folderRoot,captureId,folderId),"dom.json"),"utf8"))}catch{return null}}function styleDiffForRegions(regions,design,reference,scaleFactor,limit=5){let out=[],seen=new Set;for(let region of regions){if(out.length>=limit)break;if(!region.node)continue;let key=region.node.path.join(",");if(seen.has(key))continue;let designNode=measuredAt(design,region.node.path);if(!designNode)continue;let counterpart=counterpartOf(reference,{x:region.x/scaleFactor,y:region.y/scaleFactor,w:region.w/scaleFactor,h:region.h/scaleFactor});if(!counterpart)continue;seen.add(key);let differs=styleDifferences(designNode,counterpart),sizeDiffers=designNode.rect.w!==counterpart.rect.w||designNode.rect.h!==counterpart.rect.h;if(differs.length===0&&!sizeDiffers)continue;out.push({node:`${region.node.ref??"node"}${region.node.id?`#${region.node.id}`:""} @[${key}]`,path:region.node.path,...sizeDiffers?{size:{design:`${designNode.rect.w}\xD7${designNode.rect.h}`,page:`${counterpart.rect.w}\xD7${counterpart.rect.h}`}}:{},differs})}return out}function registerInspectTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler);mcp.registerTool("inspect",{description:"Return the node's server-rendered HTML, resolved className list, $ref, and resolved props for the node at path. Use this instead of guessing the rendered output. The HTML comes from Velloo's bundled library; where the screen mounts the app's own components (component_status { screen }), the canvas and captures show those instead, so measure them with `computed: true`. When path resolves to a snippet instance, pass innerPath to inspect a node *inside* the resolved body (args, $overrides, and $extraClassName applied) \u2014 omit it to inspect the body root. Pass `computed: true` to also render the screen in a real browser and report the node's actual geometry and resolved styles \u2014 use it instead of predicting which utility class won the cascade.",inputSchema:{screenId:string(),path:LocatorSchema,innerPath:string().optional().describe('For a snippet instance: a dotted index path like "0.2", an "@id" of a node in the body, or "" for the body root (the default). Ignored for plain components.'),computed:boolean().optional().describe("Measure the node in a real browser: box geometry and resolved computed styles, plus its direct children's boxes. Costs a render, so it's off by default."),viewport:ViewportArgSchema.optional().describe("Render size for `computed`; defaults to the folder's Desktop preset"),mode:RenderModeSchema.describe("Render variant for `computed`"),theme:ThemeNameSchema.describe("Named theme for `computed`")}},async(args)=>{let result=await inspect2(ctx,args);if(!result.ok)return errorResult(result.error);if(args.computed!==!0)return jsonResult(result.value);let screen=ctx.folder.screens.get(args.screenId);if(!screen)return errorResult(`Screen not found: ${args.screenId}`);if(args.innerPath!==void 0&&args.innerPath!=="")return jsonResult({...result.value,computed:null,computedNote:"A node inside a snippet body isn't separately addressable in the render \u2014 it carries the instance's path. Measure the instance, or render the snippet with render_snippet."});let located=resolve8(screen.tree,args.path,args.screenId);if(!located.ok)return errorResult(located.error);let path=located.value,themeName=args.theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,args.screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}let themeRes=resolveNamedTheme(ctx.folder,themeName);if(!themeRes.ok)return errorResult(themeRes.message);let defaults=defaultViewport(ctx.folder),viewport={w:args.viewport?.w??defaults.w,h:args.viewport?.h??defaults.h};try{let html=await renderForCapture(ctx,screen,{theme:themeRes.theme,dark:args.mode==="dark",viewport,snapshotCss:await jit.build(),liveUrl,canvasBundle,assetOrigin}),dom=await measureRendered({html,viewport}),node=measuredAt(dom,path),diagnostics=await mountDiagnostics(ctx,canvasBundler,screen);return jsonResult({...result.value,...diagnostics.length>0?{diagnostics}:{},computed:node?{viewport,...node,children:childrenMeasuredAt(dom,path)}:null,...node?{}:{computedNote:"The node rendered nothing measurable \u2014 it is display:none, zero-area, or its component drops the props it was given. A zero-area box is itself the finding."}})}catch(err){let timeout=captureTimeoutMessage(err,"inspect");return errorResult(timeout??`inspect: computed measurement failed: ${String(err)}`)}}),mcp.registerTool("find_nodes",{description:'Query a screen tree for nodes matching filters (ANDed): exact $ref, exact $snippet, exact $id, className substring, or prop presence/value. Use this to locate targets for update_props/move_node instead of fetching and walking the whole tree. Example: { screenId: "home", ref: "Icon", prop: "name", propValue: "Github" }.',outputSchema:FindNodesOutput,inputSchema:{screenId:string(),ref:string().optional().describe("Exact component $ref, e.g. 'Button'"),snippetId:string().optional().describe("Exact $snippet id for snippet instances"),id:string().optional().describe("Exact $id anchor"),classContains:string().optional().describe("Substring of props.className"),prop:string().optional().describe("Prop key that must be present"),propValue:unknown().optional().describe("With prop: strict-equal value match"),limit:number().int().positive().optional().describe("Max matches; default 50")}},async(args)=>{let result=await findNodes(ctx,args);return result.ok?structuredResult({...result.value}):errorResult(result.error)})}async function toMcpWithWarnings(result,warn,diagnose){if(!result.ok)return errorResult(result.error);let[propWarnings,diagnostics]=await Promise.all([warn(result.value).catch(()=>[]),diagnose?.(result.value).catch(()=>[])??[]]);return jsonResult(propWarnings.length>0||diagnostics.length>0?{...result.value,...propWarnings.length>0?{propWarnings}:{},...diagnostics.length>0?{diagnostics}:{}}:result.value)}function registerMutationTools(mcp,ctx,jit){mcp.registerTool("update_props",{description:"Patch nodes on a screen: one entry per node in `patches`, applied in one atomic write \u2014 length 1 for a single edit. `propPatch` shallow-merges props (null removes a key). `style` restyles through the screen's *native* channel, which the framework adapter routes to a Tailwind `className` string (shadcn), an `sx` object (MUI), or a plain `style` object; object channels merge shallowly and `style: null` clears. An entry may carry either or both. This patches plain screen nodes \u2014 for one inside a snippet, see velloo://guide/snippets.",inputSchema:updatePropsShape},async(args)=>toMcpWithWarnings(await updateProps2(ctx,args),async()=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return[];let all=[];for(let patch of args.patches){let resolved=resolve8(screen.tree,patch.path,args.screenId);if(!resolved.ok)continue;let node=pathAt(screen.tree,resolved.value);if(node&&isComponentNode(node))all.push(...await propWarnings(ctx,screen,node.$ref,patch.propPatch??{}))}return all},async()=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return[];let all=renderDiagnostics(ctx,screen);for(let patch of args.patches){let resolved=resolve8(screen.tree,patch.path,args.screenId);if(!resolved.ok)continue;let node=pathAt(screen.tree,resolved.value);if(node)all.push(...await diagnosticsForTree(ctx,jit,screen,node,resolved.value))}return all})),mcp.registerTool("remove_node",{description:'Remove the node at path. A root locator ([] or "@root") clears all root children; use `compose` mode "replace" when rebuilding the complete screen.',inputSchema:removeNodeShape},async(args)=>toMcp(await removeNode2(ctx,args))),mcp.registerTool("move_node",{description:"Move a node from fromPath to a new parent. toIndex is the insertion index in the destination's children.",inputSchema:moveNodeShape},async(args)=>toMcp(await moveNode2(ctx,args))),mcp.registerTool("set_node_id",{description:'Set or clear the `$id` anchor on a node. Once set, the node is addressable as "@<id>" in any path-accepting tool. Pass `id: null` to clear. Per-screen uniqueness is enforced.',inputSchema:setNodeIdShape},async(args)=>toMcp(await setNodeId2(ctx,args))),mcp.registerTool("add_screen",{description:'Create a NEW screen. It is not placed on any board \u2014 call `add_frame` to surface it. Pass `fromScreenId` to clone an existing tree, or `tree` to supply one; omit `id` to auto-suffix a unique one. A route scan already scaffolds one placeholder screen per detected route (id = route slug), so don\'t add_screen for those \u2014 rebuild the existing screen with `compose` mode "replace".',inputSchema:addScreenShape},async(args)=>toMcpWithWarnings(await addScreen2(ctx,args),async()=>{let created=args.id??"",screen=ctx.folder.screens.get(created)??[...ctx.folder.screens.values()].find((s)=>s.name===args.name);return screen?propWarningsForTree(ctx,screen,screen.tree):[]},async()=>{let created=args.id??"",screen=ctx.folder.screens.get(created)??[...ctx.folder.screens.values()].find((s)=>s.name===args.name);return screen?diagnosticsForScreen(ctx,jit,screen):[]})),mcp.registerTool("update_screen",{description:"Update screen metadata. Sparse patch \u2014 only `name` is patchable today. Screen id stays stable.",inputSchema:updateScreenShape},async(args)=>toMcp(await updateScreen2(ctx,args))),mcp.registerTool("remove_screen",{description:"Delete a screen. Refuses if it's the last screen. If any frames reference the screen, they're returned in `removedFrameIds` (cascaded removal).",inputSchema:removeScreenShape},async(args)=>toMcp(await removeScreen2(ctx,args))),mcp.registerTool("add_board",{description:"Create a new empty board \u2014 one infinite canvas of frames, and a folder can have many. `group` files it under a sidebar group: an existing group's name or id, or a new name to create that group. Guide: velloo://guide/boards.",inputSchema:addBoardShape},async(args)=>toMcp(await addBoard2(ctx,args))),mcp.registerTool("update_board",{description:"Update a board's metadata. `theme` names a theme its frames render with \u2014 the per-board look; null clears back to the folder default. `archived: true` files the board away (hidden from the sidebar, list_boards and a default publish, but kept and still editable). `group` moves it to a sidebar group by existing name or id, a new name (which creates one), or null for Ungrouped. Guide: velloo://guide/boards.",inputSchema:updateBoardShape},async(args)=>toMcp(await updateBoard2(ctx,args))),mcp.registerTool("remove_board",{description:"Delete a board and its notes. Screens no other board places are deleted with it and returned in `removedScreenIds`; screens also framed elsewhere are kept. Snippets those screens were the last to reach go too, in `removedSnippetIds` \u2014 a snippet already unreferenced before the call is left alone. Permanent \u2014 to file a board away reversibly, prefer update_board { patch: { archived: true } }.",inputSchema:removeBoardShape},async(args)=>toMcp(await removeBoard2(ctx,args))),mcp.registerTool("reorder_boards",{description:"Set the left-sidebar display order of boards. `order` lists board ids in the desired order; unknown ids are ignored and omitted boards keep their current slots.",inputSchema:reorderBoardsShape},async(args)=>toMcp(await reorderBoards2(ctx,args))),mcp.registerTool("update_viewport_presets",{description:"Replace the folder's viewport presets \u2014 the sizes offered when adding a frame. Send the complete list in display order; names must be distinct and at least one is required. Frames store their own w/h, so this never resizes an existing frame.",inputSchema:updateViewportPresetsShape},async(args)=>toMcp(await updateViewportPresets2(ctx,args))),mcp.registerTool("add_frame",{description:"Place a screen on a specific board at a chosen size + position. x/y default to a free spot on that board.",inputSchema:addFrameShape},async(args)=>toMcp(await addFrame2(ctx,args))),mcp.registerTool("update_frame",{description:"Move, resize, relabel or regroup frames on a board: one entry per frame in `patches`, applied in one atomic write \u2014 length 1 for a single frame. `label`, `group` and `scheme` accept null to clear; an omitted field is unchanged. `scheme` pins a frame's render mode \u2014 a review affordance over the screen's one shared tree, not a separate design variant.",inputSchema:updateFrameShape},async(args)=>toMcp(await updateFrames2(ctx,args))),mcp.registerTool("remove_frame",{description:"Remove a frame placement from a board. The underlying screen is left intact.",inputSchema:removeFrameShape},async(args)=>toMcp(await removeFrame2(ctx,args))),mcp.registerTool("add_snippet",{description:'Create a reusable subtree with typed `params`; placeholders in the body are `{ "$param": "name" }`. **Structure that varies between instances is still ONE snippet: declare a `node` param** rather than inlining the repetition \u2014 the most expensive mistake here. Call `render_snippet` afterwards; $param wiring bugs are silent until instantiation. Guide: velloo://guide/snippets.',inputSchema:addSnippetShape},async(args)=>toMcpWithWarnings(await addSnippet2(ctx,args),async(value)=>dynamicIconWarningsForTree(value.snippet.tree),async(value)=>diagnosticsForTree(ctx,jit,value.snippet,value.snippet.tree))),mcp.registerTool("update_snippet",{description:"Update a snippet's metadata or body; sparse patch, and every screen using it is re-broadcast. `innerPatch` retargets one node inside the body across every instance without resending the tree; pass `tree` only for a full body replacement. Guide: velloo://guide/snippets.",inputSchema:updateSnippetShape},async(args)=>toMcpWithWarnings(await updateSnippet2(ctx,args),async(value)=>{let warnings=[...await dynamicIconWarningsForTree(value.snippet.tree)];if(args.patch.params!==void 0){let declared=new Set(value.snippet.params.map((p)=>p.name));for(let inst of findSnippetInstances(ctx.folder,value.snippet.id)){let{missing}=resolveSnippetArgs(value.snippet,inst.args),extras=Object.keys(inst.args).filter((k)=>!declared.has(k));if(missing.length===0&&extras.length===0)continue;let parts=[missing.length?`missing required: ${missing.join(", ")}`:"",extras.length?`unknown: ${extras.join(", ")}`:""].filter(Boolean).join("; "),at=inst.path===""?"the root":`path [${inst.path.replace(/\./g,",")}]`;warnings.push(`param change strands the instance on "${inst.screenId}" at ${at} (${parts}) \u2014 fix its args with update_snippet_instance before rendering that screen`)}}return warnings},async(value)=>diagnosticsForTree(ctx,jit,value.snippet,value.snippet.tree))),mcp.registerTool("remove_snippet",{description:"Delete a snippet. Refuses with SnippetInUse while anything still instantiates it; the payload splits the referencers into `screenIds` (clear with remove_node) and `snippetIds` \u2014 other snippets whose body embeds this one (clear with update_snippet).",inputSchema:removeSnippetShape},async(args)=>toMcp(await removeSnippet2(ctx,args))),mcp.registerTool("update_snippet_instance",{description:"Edit ONE snippet instance without touching the shared definition. `argPatch` changes what the caller passes in; `extraClassName` replaces its per-instance class suffix; `innerPath` + `propPatch` patch a node inside just this instance's body \u2014 the \"this card's badge is red\" escape hatch. They compose in one call. To change every instance instead, use `update_snippet`'s innerPatch. Guide: velloo://guide/snippets.",inputSchema:updateSnippetInstanceShape},async(args)=>{let plan=normalizeUpdateSnippetInstance(args);if(!plan.ok)return errorResult(badRequest(plan.message,plan.issues));return toMcpWithWarnings(await updateSnippetInstance(ctx,plan.args),async()=>[],async()=>{let screen=ctx.folder.screens.get(args.screenId);return screen?diagnosticsForScreen(ctx,jit,screen):[]})})}function registerNoteTools(mcp,ctx){mcp.registerTool("add_note",{description:"Add a sticky note to a board. Either free-floating at board coordinates (same space as frame x/y; place beside frames, not on them) or attached to a node \u2014 pass `attachment` and the canvas anchors the note to that node with a connector, auto-placing it beside the frame. body is markdown-lite.",inputSchema:{boardId:string(),x:number().optional().describe("Board x; required unless `attachment` is given"),y:number().optional().describe("Board y; required unless `attachment` is given"),width:number().positive().optional().describe("Pixel width; default 240"),body:string(),attachment:object({frameId:string().describe("Frame on this board that shows the screen"),screenId:string(),locator:LocatorSchema}).optional().describe("Anchor the note to a node instead of free board space")}},async(args)=>toMcp(await addNote2(ctx,args))),mcp.registerTool("update_note",{description:"Patch a board note's position, width, or body.",inputSchema:{boardId:string(),noteId:string(),patch:object({x:number().optional(),y:number().optional(),width:number().positive().optional(),body:string().optional()})}},async(args)=>toMcp(await updateNote2(ctx,args))),mcp.registerTool("remove_note",{description:"Remove a board note by id.",inputSchema:{boardId:string(),noteId:string()}},async(args)=>toMcp(await removeNote2(ctx,args))),mcp.registerTool("add_annotation",{description:'Pin a markdown annotation to a specific node (questions for the designer, review remarks). The canvas draws a connector to the node in every frame showing it. Created with author: "agent" \u2014 you may remove your own annotations later, but user-authored ones are read-only to you.',inputSchema:{screenId:string(),path:LocatorSchema,body:string(),collapsed:boolean().optional()}},async({screenId,path,body,collapsed})=>toMcp(await addAnnotation2(ctx,{screenId,target:{locator:path},body,collapsed,author:"agent"}))),mcp.registerTool("update_annotation",{description:"Edit one of your own annotations \u2014 its body, or `collapsed` (null clears the override). Refuses user-authored ones: those are the designer's channel to you, so answer them with your own annotation rather than rewriting theirs.",inputSchema:{screenId:string(),annotationId:string(),body:string().optional(),collapsed:boolean().nullable().optional()}},async({screenId,annotationId,body,collapsed})=>{if(body===void 0&&collapsed===void 0)return errorResult(badRequest("update_annotation: pass body, collapsed, or both."));let existing=ctx.folder.annotations.get(screenId)?.find((a)=>a.id===annotationId);if(existing&&existing.author!=="agent")return errorResult(badRequest(`Annotation ${annotationId} is user-authored \u2014 agents may only edit their own.`));return toMcp(await updateAnnotation2(ctx,{screenId,annotationId,patch:{...body!==void 0?{body}:{},...collapsed!==void 0?{collapsed}:{}}}))}),mcp.registerTool("remove_annotation",{description:"Remove an agent-authored annotation. Refuses user-authored ones \u2014 those are the designer's channel to you; act on them, don't delete them.",inputSchema:{screenId:string(),annotationId:string()}},async({screenId,annotationId})=>{let existing=ctx.folder.annotations.get(screenId)?.find((a)=>a.id===annotationId);if(existing&&existing.author!=="agent")return errorResult(badRequest(`Annotation ${annotationId} is user-authored \u2014 agents may only remove their own.`));return toMcp(await removeAnnotation2(ctx,{screenId,annotationId}))})}import{readFileSync as readFileSync8}from"fs";import{isAbsolute as isAbsolute5,join as join33}from"path";function urlCacheKey(p){return JSON.stringify({url:p.url,w:p.w,h:p.h,fullPage:p.fullPage,scale:p.scale,dark:p.dark,storageStatePath:p.storageStatePath,cookies:p.cookies??null,localStorage:p.localStorage??null})}function planUrlCache(opts){let{cacheUrl,refreshUrl,prior,now,ttlMs}=opts,active=cacheUrl||refreshUrl,reuse=cacheUrl&&!refreshUrl&&prior&&now-prior.capturedAt<=ttlMs?prior:null;return{active,reuse,storeEligible:active&&reuse===null}}class LruMap{cap;entries=new Map;bytes=0;maxBytes;sizeOf;constructor(cap,opts={}){this.cap=cap;this.maxBytes=opts.maxBytes,this.sizeOf=opts.sizeOf??(()=>0)}get(key){return this.entries.get(key)}set(key,value){let prev=this.entries.get(key);if(prev!==void 0)this.bytes-=this.sizeOf(prev);this.entries.delete(key),this.entries.set(key,value),this.bytes+=this.sizeOf(value);while(this.entries.size>this.cap||this.maxBytes!==void 0&&this.bytes>this.maxBytes&&this.entries.size>1){let oldest=this.entries.keys().next().value;if(oldest===void 0)break;let v=this.entries.get(oldest);if(v!==void 0)this.bytes-=this.sizeOf(v);this.entries.delete(oldest)}}get size(){return this.entries.size}}var URL_CACHE_CAP=20,URL_CACHE_MAX_BYTES=67108864,DEFAULT_URL_CACHE_TTL_MS=300000;function storedCaptureRaster(png,manifest,fullPage){let screenshotMode=manifest.geometry?.screenshot?.mode;if(fullPage&&screenshotMode==="viewport")return{ok:!1,message:`capture "${manifest.id}" contains only its viewport. Pass fullPage: false or make a full-page capture.`};if(!fullPage&&screenshotMode!=="viewport"){let size=pngSize(png),viewportWidth=manifest.geometry?.viewportCss.width??manifest.viewport.w,viewportHeight=manifest.geometry?.viewportCss.height??manifest.viewport.h,scale=viewportWidth>0?size.width/viewportWidth:1,scroll=manifest.geometry?.scrollCss??{x:0,y:0};return{ok:!0,png:cropPng(png,{x:Math.round(scroll.x*scale),y:Math.round(scroll.y*scale),w:Math.round(viewportWidth*scale),h:Math.round(viewportHeight*scale)},0)}}return{ok:!0,png}}function storedCaptureDom(dom,geometry,fullPage){if(fullPage||!geometry)return dom;let{x,y}=geometry.scrollCss;return{...dom,viewport:{w:geometry.viewportCss.width,h:geometry.viewportCss.height},documentHeight:geometry.viewportCss.height,nodes:dom.nodes.map((node)=>({...node,rect:{...node.rect,x:node.rect.x-x,y:node.rect.y-y}}))}}var UrlCookieSchema=object({name:string(),value:string(),url:string().optional(),domain:string().optional(),path:string().optional()}),LiveUrlSource=strictObject({url:string().describe("e.g. http://localhost:3000/pricing"),auth:strictObject({storageStatePath:string().optional().describe("Playwright storage-state JSON (logged-in cookies + localStorage) \u2014 absolute, or relative to the design folder. The robust way past an auth gate."),cookies:array(UrlCookieSchema).optional().describe("Session cookies seeded before navigating (each needs url OR domain+path)"),localStorage:record(string(),string()).optional().describe("localStorage entries seeded before any page script runs (e.g. a JWT)")}).optional().describe("Credentials for a page behind a login"),settleTimeoutMs:number().int().positive().optional().describe("Max ms to wait for network quiet before capturing (default 8000). Raise it for a data-heavy page that paints a spinner first, so the shot fires once async data has settled rather than capturing the spinner (which reads as unverified)."),cache:strictObject({freeze:boolean().optional().describe("Capture the page once and diff every later call against that frozen reference. Essential for a DYNAMIC page (feed, dashboard, per-user content): without it each call re-fetches, the content drifts, and `similarity` jitters so you cannot tell a design change from a page change."),ttlMs:number().int().positive().optional().describe("How long a frozen capture stays valid; default 300000 (5 min)"),refresh:boolean().optional().describe("Re-capture now and replace the frozen reference \u2014 after fixing the target app. Implies freeze.")}).optional()}),StoredCaptureSource=strictObject({captureId:string().describe("From list_captures")});function similarityNote(input){let{similarity,contentSimilarity,heightDelta}=input,heightDiffers=heightDelta!==0;if(heightDiffers&&contentSimilarity-similarity>=0.05)return`similarity is held down mostly by a ${Math.abs(heightDelta)}px height difference, not by content mismatch \u2014 `+`over the overlapping height the match is ${contentSimilarity}. Small cumulative vertical drift cascades down a long single column and tanks the whole-page pixel diff; trust contentSimilarity and the per-region node refs here.`;if(similarity>=0.3)return null;return`similarity ${similarity} means these two renders differ structurally, not in detail. At this range the diff cannot localize: `+"topMismatches will span most of the page with a null node ref, which restates the score rather than naming a cause \u2014 do not work through them yet. "+(heightDiffers?`The render is ${Math.abs(heightDelta)}px ${heightDelta>0?"taller":"shorter"} than the page (${contentSimilarity} over the overlap), which is itself the lead: at this size the gap is whole sections missing, `+"stacked where the page puts them side by side, or a container a different width \u2014 not spacing. ":"")+"Re-read the capture's outline and rects (get_capture) and fix the page-level structure first; topMismatches start earning their keep past ~0.4."}function registerCompareToUrlTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler),urlCaptures=new LruMap(URL_CACHE_CAP,{maxBytes:URL_CACHE_MAX_BYTES,sizeOf:(c)=>c.result.png.byteLength});mcp.registerTool("compare_to_url",{description:"Code-to-design fidelity check: render a screen and capture the same page from a live URL (or a stored `captureId`) at the same viewport, then pixel-diff. 0.85+ is a faithful structural port; fix `topMismatches` in order and don't chase 1.0. `styleDiff` names the resolved computed properties behind each of those regions (design vs page) \u2014 read it instead of inferring from class strings which utility won. **If the result is `unverified` the similarity is meaningless \u2014 stop and fix the capture rather than iterating against a page you never saw.** Guide: velloo://guide/porting.",inputSchema:{screenId:string(),source:union([LiveUrlSource,StoredCaptureSource]).describe("What to diff against: a live page, or a capture already on disk"),viewport:ViewportArgSchema.optional().describe("Render size; defaults to the folder's Desktop preset"),mode:_enum(["light","dark"]).optional(),fullPage:boolean().optional(),scale:number().min(0.25).max(1).optional().describe("Default 0.5"),theme:ThemeNameSchema.describe("Named theme for the Velloo side; default = the hosting board's pin, else the folder default"),image:boolean().optional().describe("Include the side-by-side PNG (default true; false = metrics only)")},outputSchema:CompareToUrlOutput},async({screenId,source,viewport:vp,mode,fullPage,scale,theme,image})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(`Screen not found: ${screenId}`);let live="url"in source?source:null,url=live?.url,captureId=live?void 0:source.captureId,{storageStatePath,cookies,localStorage}=live?.auth??{},settleTimeoutMs=live?.settleTimeoutMs,cache=live?.cache,themeName=theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}let storedPng=null,storedFinalUrl="",storedViewport=null,storedGeometry,storedStateId;if(captureId!==void 0){if(!isSafeCaptureId(captureId))return errorResult(`Invalid captureId: ${captureId}`);let folderId=ctx.folder.config.folderId,manifest=readCaptureManifest(ctx.folder.root,captureId,folderId);if(!manifest)return errorResult(`compare_to_url: no capture "${captureId}" for this folder \u2014 call list_captures to see what's stored.`);if(manifest.themeOnly||!manifest.files.includes("page.png"))return errorResult(`compare_to_url: capture "${captureId}" is theme-only, so there's no page render to diff against. Capture the page itself in a capture session.`);if(manifest.stability?.status==="unstable")return errorResult(`compare_to_url: capture "${captureId}" changed while its screenshot and outline were being recorded. Re-capture the page once it has settled; Velloo will not present mixed states as a frozen reference.`);try{storedPng=readFileSync8(join33(captureDir(ctx.folder.root,captureId,folderId),"page.png"))}catch{return errorResult(`compare_to_url: capture "${captureId}" is missing its page.png.`)}if(storedFinalUrl=manifest.finalUrl||manifest.url,storedGeometry=manifest.geometry,storedStateId=manifest.stateId,manifest.viewport.w>0)storedViewport=manifest.viewport;let raster=storedCaptureRaster(storedPng,manifest,fullPage!==!1);if(!raster.ok)return errorResult(`compare_to_url: ${raster.message}`);storedPng=raster.png}else{let scheme;try{scheme=new URL(url).protocol}catch{return errorResult(`Invalid url: ${url}`)}if(scheme!=="http:"&&scheme!=="https:")return errorResult(`compare_to_url: only http(s) URLs are allowed (got ${scheme})`)}let defaults=defaultViewport(ctx.folder),viewport={w:vp?.w??defaults.w,h:vp?.h??defaults.h},scaleFactor=scale??0.5,captureDownscale=1,captureResize=null;if(storedPng&&storedViewport){let requested=scale??0.5;scaleFactor=[1,0.5,0.25].reduce((best,s)=>Math.abs(s-requested)<Math.abs(best-requested)?s:best);let storedSize=pngSize(storedPng),ratio=storedSize.width/storedViewport.w/scaleFactor;if(Number.isInteger(ratio)&&ratio>=1)captureDownscale=ratio;else captureResize={width:Math.round(storedSize.width/ratio),height:Math.round(storedSize.height/ratio)};viewport={w:storedViewport.w,h:storedViewport.h||defaults.h}}try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let html=await renderForCapture(ctx,screen,{theme:_themeRes.theme,dark:mode==="dark",viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin}),resolvedStorageState=storageStatePath===void 0?void 0:isAbsolute5(storageStatePath)?storageStatePath:join33(ctx.folder.root,storageStatePath),urlKey=urlCacheKey({url:url??"",w:viewport.w,h:viewport.h,fullPage:fullPage??!0,scale:scaleFactor,dark:mode==="dark",storageStatePath:resolvedStorageState??null,cookies,localStorage}),now=Date.now(),plan=planUrlCache({cacheUrl:storedPng===null&&cache?.freeze===!0,refreshUrl:storedPng===null&&cache?.refresh===!0,prior:urlCaptures.get(urlKey),now,ttlMs:cache?.ttlMs??DEFAULT_URL_CACHE_TTL_MS}),[velloo,urlCapture]=await Promise.all([captureScreenshot({html,viewport,fullPage:fullPage??!0,deviceScaleFactor:scaleFactor,dom:!0}),storedPng?Promise.resolve({png:captureResize?resizePng(storedPng,captureResize.width,captureResize.height):downscalePng(storedPng,captureDownscale),finalUrl:storedFinalUrl,authWall:!1,pageError:null}):plan.reuse?Promise.resolve(plan.reuse.result):captureUrlScreenshot({url,viewport,fullPage:fullPage??!0,deviceScaleFactor:scaleFactor,dark:mode==="dark",dom:!0,...settleTimeoutMs!==void 0?{settleTimeoutMs}:{},...resolvedStorageState?{storageStatePath:resolvedStorageState}:{},...cookies?{cookies}:{},...localStorage?{localStorage}:{}})]),result=diffPngs(urlCapture.png,velloo.png),regions=result.regions.map((r)=>({...r,node:regionNode(r,velloo.nodeRects,scaleFactor,screen)})),totalChanged=Math.max(1,result.changedPixels),topMismatches=regions.slice(0,5).map((r)=>{let share=Math.round((r.changedPixels??0)/totalChanged*100),node=r.node?` \u2192 ${r.node.ref??"node"}${r.node.id?`#${r.node.id}`:""} @[${r.node.path.join(",")}]`:"";return`~${share}% of the diff at (${r.x},${r.y} ${r.w}\xD7${r.h})${node}`}),rawReferenceDom=urlCapture.dom??(captureId!==void 0?readCaptureDom(ctx.folder.root,captureId,ctx.folder.config.folderId):null),referenceDom=rawReferenceDom?storedCaptureDom(rawReferenceDom,storedGeometry,fullPage!==!1):null,styleDiff=velloo.dom&&referenceDom?styleDiffForRegions(regions,velloo.dom,referenceDom,scaleFactor):[],cls=storedPng?{redirected:!1,finalLooksLikeLogin:!1,requestedLooksLikeLogin:!1}:classifyCapture(url,urlCapture.finalUrl),authWall=urlCapture.authWall&&!cls.requestedLooksLikeLogin,unverified=cls.redirected||authWall||urlCapture.pageError!==null;if(plan.storeEligible&&!unverified)urlCaptures.set(urlKey,{result:urlCapture,capturedAt:now});let reason=cls.redirected?`it redirected to ${urlCapture.finalUrl}`:urlCapture.pageError!==null?urlCapture.pageError:"it shows a login form",contentHeight=contentHeightFromRects(velloo.nodeRects),shortFrames=framesShorterThan(ctx,screenId,contentHeight,viewport.w),similarity=Number((1-result.changedRatio).toFixed(4)),contentSimilarity=Number((1-result.contentChangedRatio).toFixed(4)),bitmapHeightDelta=result.heightDelta,heightDelta=Number((bitmapHeightDelta/scaleFactor).toFixed(2)),heightDiffers=heightDelta!==0,note=similarityNote({similarity,contentSimilarity,heightDelta}),diagnostics=[...await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]),...await mountDiagnostics(ctx,canvasBundler,screen)],summary={similarity,changedRatio:Number(result.changedRatio.toFixed(4)),...heightDiffers?{contentSimilarity}:{},heightDelta,bitmapHeightDelta,contentHeight,...shortFrames.length?{framesShorterThanContent:shortFrames}:{},...!unverified&¬e!==null?{note}:{},...storedPng?{capture:{id:captureId,url:storedFinalUrl,...storedGeometry?{geometry:storedGeometry}:{},...storedStateId?{stateId:storedStateId}:{},note:"diffed against a stored browser capture \u2014 already past any login and frozen, so similarity reflects your design changes only."}}:{},...plan.active?{urlCache:plan.reuse?{hit:!0,capturedAt:new Date(plan.reuse.capturedAt).toISOString(),ageMs:now-plan.reuse.capturedAt,note:"diffed against a frozen capture \u2014 similarity reflects design changes only, not page drift. Pass cache.refresh to re-sample."}:{hit:!1,stored:!unverified}}:{},...topMismatches.length?{topMismatches}:{},...styleDiff.length?{styleDiff}:{},regions,...diagnostics.length>0?{diagnostics}:{},...unverified?{unverified:!0,...cls.redirected?{redirected:{requested:url??"",final:urlCapture.finalUrl}}:{},...authWall?{authWall:!0}:{},...urlCapture.pageError!==null?{pageError:urlCapture.pageError}:{},warning:`the capture is NOT a faithful view of your page \u2014 ${reason}. similarity is meaningless here (you're diffing against the wrong thing). For an auth wall, pass storageStatePath/cookies/localStorage; for a broken target, fix the app's dev server first. `+"Either way, don't tune the design to this capture \u2014 leave the screen unverified."}:{}},extra=[];if(image!==!1){let side=sideBySidePng(urlCapture.png,velloo.png);extra.push({type:"image",data:side.toString("base64"),mimeType:"image/png"})}return structuredResult(summary,...extra)}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"compare_to_url");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);if(/net::|ERR_CONNECTION|Timeout.*exceeded|goto/.test(msg))return errorResult(`compare_to_url: could not load ${url??captureId} \u2014 is the app's dev server running? Underlying error: ${msg}`);return errorResult(`compare_to_url failed: ${msg}`)}})}function registerRenderSnippetTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler);mcp.registerTool("render_snippet",{description:"Render a snippet in isolation and return a screenshot plus any class/theme diagnostics. Useful for iterating on a snippet's styling before stamping it. viewport defaults to 480\xD7640.",inputSchema:{snippetId:string(),args:record(string(),unknown()).optional(),extraClassName:string().optional(),viewport:ViewportArgSchema.optional().describe("Defaults to 480\xD7640"),mode:RenderModeSchema,scale:number().min(0.25).max(1).optional(),theme:ThemeNameSchema}},async({snippetId,args,extraClassName,viewport,mode,scale,theme})=>{let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return errorResult(`Snippet not found: ${snippetId}`);let vp=viewport??{w:480,h:640},screen={id:`${snippet.id}__preview`,name:`${snippet.name} preview`,tree:{$snippet:snippet.id,...args&&Object.keys(args).length>0?{args}:{},...extraClassName&&extraClassName.trim()!==""?{$extraClassName:extraClassName.trim()}:{}}},buf;try{let extraCandidates=[];if(extraClassName)extraCandidates.push(...extraClassName.split(/\s+/));for(let v of Object.values(args??{}))if(typeof v==="string")extraCandidates.push(...v.split(/\s+/));let snapshotCss=await jit.build(extraCandidates.filter(Boolean)),syntheticScreen={...screen,library:snippet.library},_themeRes=resolveNamedTheme(ctx.folder,theme);if(!_themeRes.ok)return errorResult(_themeRes.message);let base={theme:_themeRes.theme,viewport:vp,snapshotCss,liveUrl,canvasBundle,assetOrigin};if(mode==="compare"){let[lightHtml,darkHtml]=await Promise.all([renderForCapture(ctx,syntheticScreen,{...base,dark:!1}),renderForCapture(ctx,syntheticScreen,{...base,dark:!0})]);buf=await screenshotCompareBuffer({leftHtml:lightHtml,rightHtml:darkHtml,viewport:vp,...scale?{deviceScaleFactor:scale}:{}})}else{let html=await renderForCapture(ctx,syntheticScreen,{...base,dark:mode==="dark"});buf=await screenshotBuffer({html,viewport:vp,fullPage:!0,...scale?{deviceScaleFactor:scale}:{}})}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"render_snippet");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`render_snippet failed: ${msg}`)}let diagnostics=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);return{content:[...diagnostics.length>0?[{type:"text",text:JSON.stringify({diagnostics})}]:[],{type:"image",data:buf.toString("base64"),mimeType:"image/png"}]}})}var BASELINE_CAP=20,BASELINE_MAX_BYTES=67108864;function resolveScreenshotMode(folder,screenId,explicit){if(explicit!==void 0)return{ok:!0,mode:explicit};let pinned=pinnedSchemeForScreen(folder,screenId);if(!pinned.ok)return pinned;return{ok:!0,mode:pinned.scheme??"light"}}function registerScreenshotCaptureTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler),baselines=new LruMap(BASELINE_CAP,{maxBytes:BASELINE_MAX_BYTES,sizeOf:(b)=>b.png.byteLength});mcp.registerTool("screenshot",{description:"Render a screen to PNG and run full class/theme diagnostics. `mode: \"compare\"` returns light and dark side by side \u2014 the fastest check that a design adapts; omitted, mode follows the hosting frame's pin. `diff: true` compares against your previous capture. `scale` (0.25\u20131) shrinks the payload; `path` captures one element. The render uses its OWN viewport, not the board frame's, so `framesShorterThanContent` names placements that clip below the fold \u2014 resize them with `update_frame`. Guide: velloo://guide/verification.",inputSchema:{screenId:string(),viewport:ViewportArgSchema.optional().describe("Render size; defaults to the folder's Desktop preset"),mode:RenderModeSchema,fullPage:boolean().optional(),scale:number().min(0.25).max(1).optional(),path:LocatorSchema.optional().describe('Capture only this node \u2014 path array or "@id" (not with mode: "compare"). Omit it (or pass "@root"/[]) to capture the whole screen, which is the default.'),theme:ThemeNameSchema.describe("Named theme to render with; default = the hosting board's pin, else the folder default"),diff:boolean().optional().describe("Compare against the previous same-params capture"),resetBaseline:boolean().optional()}},async({screenId,viewport:vp,mode,fullPage,scale,path,theme,diff,resetBaseline})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(`Screen not found: ${screenId}`);let diagnostics=[...await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]),...await mountDiagnostics(ctx,canvasBundler,screen)],withDiagnostics=(value)=>({...value,...diagnostics.length>0?{diagnostics}:{}}),resolvedMode=resolveScreenshotMode(ctx.folder,screenId,mode);if(!resolvedMode.ok)return errorResult(resolvedMode.message);mode=resolvedMode.mode;let themeName=theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}if(diff&&(mode==="compare"||path!==void 0))return errorResult('screenshot: diff cannot combine with mode: "compare" or path');let clipSelector;if(path!==void 0){if(mode==="compare")return errorResult('screenshot: path cannot be combined with mode: "compare"');if(!(path==="@root"||Array.isArray(path)&&path.length===0||typeof path==="string"&&path.replace(/\s/g,"")==="[]")){let resolved=resolve8(screen.tree,path,screenId);if(!resolved.ok)return errorResult(JSON.stringify(resolved.error));clipSelector=`[data-node-path="${resolved.value.join(".")}"]`}}let defaults=defaultViewport(ctx.folder),viewport={w:vp?.w??defaults.w,h:vp?.h??defaults.h};if(diff)try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let html=await renderForCapture(ctx,screen,{theme:_themeRes.theme,dark:mode==="dark",viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin}),capture=await captureScreenshot({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{}}),key=JSON.stringify({screenId,w:viewport.w,h:viewport.h,mode,theme:themeName,fullPage:fullPage??!0,scale:scale??1}),baseline=baselines.get(key);if(baselines.set(key,{png:capture.png,rects:capture.nodeRects}),!baseline||resetBaseline)return{content:[{type:"text",text:JSON.stringify(withDiagnostics({diff:{baseline:"established"}}))},{type:"image",data:capture.png.toString("base64"),mimeType:"image/png"}]};let result=diffPngs(baseline.png,capture.png),scaleFactor=scale??1,regions=result.regions.map((r)=>({...r,node:regionNode(r,capture.nodeRects,scaleFactor,screen)})),summary=withDiagnostics({diff:{changedRatio:Number(result.changedRatio.toFixed(4)),changedPixels:result.changedPixels,heightDelta:result.heightDelta,regions,baseline:"updated"}});if(result.changedPixels===0)return{content:[{type:"text",text:JSON.stringify(withDiagnostics({diff:{changedRatio:0,note:"no visual change"}}))}]};if(result.changedRatio<0.4&®ions.length>0){let crop=cropPng(result.diffPng,unionRegion(result.regions));return{content:[{type:"text",text:JSON.stringify(summary)},{type:"image",data:crop.toString("base64"),mimeType:"image/png"}]}}return{content:[{type:"text",text:JSON.stringify({...summary,note:"changes too widespread for an overlay \u2014 new capture follows"})},{type:"image",data:capture.png.toString("base64"),mimeType:"image/png"}]}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"screenshot diff");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`screenshot diff failed: ${msg}`)}let buf,contentText=null;try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let base={theme:_themeRes.theme,viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin};if(mode==="compare"){let[lightHtml,darkHtml]=await Promise.all([renderForCapture(ctx,screen,{...base,dark:!1}),renderForCapture(ctx,screen,{...base,dark:!0})]);buf=await screenshotCompareBuffer({leftHtml:lightHtml,rightHtml:darkHtml,viewport,...scale?{deviceScaleFactor:scale}:{}})}else{let html=await renderForCapture(ctx,screen,{...base,dark:mode==="dark"});if(clipSelector)buf=await screenshotBuffer({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{},clipSelector});else{let capture=await captureScreenshot({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{}});buf=capture.png;let contentHeight=contentHeightFromRects(capture.nodeRects),shortFrames=framesShorterThan(ctx,screenId,contentHeight,viewport.w);contentText=JSON.stringify(withDiagnostics({contentHeight,theme:themeName??"default",viewport:{w:viewport.w,h:viewport.h},...shortFrames.length?{framesShorterThanContent:shortFrames}:{}}))}}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"screenshot");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`screenshot failed: ${msg}`)}let content=[];if(contentText)content.push({type:"text",text:contentText});else if(diagnostics.length>0)content.push({type:"text",text:JSON.stringify({diagnostics})});return content.push({type:"image",data:buf.toString("base64"),mimeType:"image/png"}),{content}})}function registerScreenshotTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){registerScreenshotCaptureTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerCompareToUrlTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerRenderSnippetTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin)}import{readFile as readFile14}from"fs/promises";import{dirname as dirname16,isAbsolute as isAbsolute6,join as join34}from"path";var CHART_LIBS=["recharts","echarts","echarts-for-react","chart.js","react-chartjs-2","victory","plotly.js","react-plotly.js","@observablehq/plot","@tremor/react","react-vis","d3","@visx","@nivo"];function chartLibsInDeps(deps){let found=new Set;for(let name of Object.keys(deps))for(let lib of CHART_LIBS)if(name===lib||lib.startsWith("@")&&name.startsWith(`${lib}/`))found.add(lib);return[...found].sort()}var TW_CONFIG_NAMES=["tailwind.config.ts","tailwind.config.js","tailwind.config.mjs","tailwind.config.cjs"];async function readTailwindConfig(explicitPath,cssResolvedPath,folderRoot){if(explicitPath){let p=isAbsolute6(explicitPath)?explicitPath:join34(folderRoot,explicitPath);return readFile14(p,"utf8").catch(()=>null)}if(!cssResolvedPath)return null;let dir=dirname16(cssResolvedPath);for(let i=0;i<5;i++){for(let name of TW_CONFIG_NAMES){let hit=await readFile14(join34(dir,name),"utf8").catch(()=>null);if(hit!==null)return hit}let parent=dirname16(dir);if(parent===dir)break;dir=parent}return null}async function readHostPackageJson(folder,cssResolvedPath){let candidates=[];if(folder.config.hostApp?.root)candidates.push(join34(hostAppRootFrom(folder.root,folder.config.hostApp),"package.json"));if(cssResolvedPath){let dir=dirname16(cssResolvedPath);for(let i=0;i<6;i++){candidates.push(join34(dir,"package.json"));let parent=dirname16(dir);if(parent===dir)break;dir=parent}}for(let p of candidates){let txt=await readFile14(p,"utf8").catch(()=>null);if(txt===null)continue;try{return JSON.parse(txt)}catch{}}return null}async function detectChartLibs(folder,cssResolvedPath){let pkg=await readHostPackageJson(folder,cssResolvedPath);if(!pkg)return[];let deps={...pkg.dependencies??{},...pkg.devDependencies??{}};return chartLibsInDeps(deps)}function detectContainer(src){if(src===null)return null;let detected=parseTailwindContainer(src);if(!detected)return null;let suggestedClasses=containerClasses(detected);return{detected,suggestedClasses,note:'applied to the theme \u2014 `class="container"` now centers/pads/caps to match the app. `suggestedClasses` are the equivalent utilities if you\'d rather wrap content in a `Box` explicitly.'}}function registerThemeTools(mcp,ctx){mcp.registerTool("set_theme",{description:"Edit the theme through one verb \u2014 pass any combination of channels. `tokens` patches dot-paths (`colors.*` / `colorsDark.*` are the semantic slots that theme-flip; `palette.*` is a non-flipping passthrough for raw brand colors). `fonts` declares font roles. `typeset` sets the type rhythm the whole ladder derives from \u2014 reach for it instead of per-node `text-*`. `customCss` replaces theme/custom.css. `from` reseeds from a preset or seed color before the other channels apply. Guide: velloo://guide/theme.",inputSchema:{theme:string().optional().describe('Named theme to edit; default "default"'),tokens:record(string(),union([string(),number()])).optional().describe('Dot-path patch, e.g. { "colors.primary.DEFAULT": "#4f46e5" }'),from:union([object({preset:_enum(PRESET_NAMES)}),object({seedColor:string()})]).optional().describe("Reseed the whole palette; applied before the other channels"),fonts:array(object({role:string().describe('Token role, e.g. "display", "sans", "mono" \u2014 any utility-safe name'),family:string().optional().describe('Family name, e.g. "Unbounded". Required unless removing'),fallback:string().optional().describe("CSS stack tail; sensible default per role"),google:union([string(),literal(!0)]).optional().describe('Google Fonts axis spec ("wght@400..900") or true for a plain load'),remove:boolean().optional().describe("Drop this role; refused while a typeset still names it")})).min(1).optional(),typeset:array(object({name:string().optional().describe('Typeset name; default "default" (the folder baseline). Any other name becomes a preset a Prose region opts into'),renameTo:string().optional(),remove:boolean().optional(),size:union([string(),number(),_null()]).optional().describe('Base text size \u2014 "1em" (container-relative), 15, or "15px"'),leading:union([number(),_null()]).optional().describe("Body line-height, unitless. The heading ladder derives from it"),flow:union([string(),number(),_null()]).optional().describe('Space between blocks \u2014 e.g. "1.25em"'),fontBody:union([string(),_null()]).optional().describe("A role from `fonts`"),fontHeading:union([string(),_null()]).optional().describe("A role from `fonts`"),fontMono:union([string(),_null()]).optional().describe("A role from `fonts`")})).min(1).optional(),customCss:string().optional().describe("Replaces theme/custom.css entirely \u2014 read it first via get_theme")}},async(args)=>{let channels=["from","tokens","fonts","typeset","customCss"];if(channels.every((c)=>args[c]===void 0))return errorResult({kind:"BadRequest",message:`set_theme: pass at least one of ${channels.join(", ")}.`});let applied={};if(args.from){let seeded="preset"in args.from?await applyPreset2(ctx,args.from.preset,args.theme):await derivePaletteFromColor(ctx,args.from.seedColor,args.theme);if(!seeded.ok)return errorResult(seeded.error);applied.from=args.from}if(args.tokens){let entries=Object.entries(args.tokens).map(([path,value])=>({path,value})),r=await setTokens2(ctx,entries,args.theme);if(!r.ok)return errorResult(r.error);applied.tokens=r.value.applied;let warnings=[];for(let path of r.value.applied){let name=/^palette(?:Dark)?\.([a-z0-9-]+)$/.exec(path)?.[1];if(name&&SEMANTIC_SLOTS.has(name))warnings.push(`\`${path}\` shadows the semantic slot \`${name}\` and is skipped on emit and in the canvas (the semantic \`--color-${name}\` wins). Set \`colors.${name}\` / \`colorsDark.${name}\` instead, or rename the palette token (e.g. \`brand-${name}\`).`)}if(warnings.length>0)applied.warnings=warnings}if(args.fonts){let r=await setFonts2(ctx,args.fonts,args.theme);if(!r.ok)return errorResult(r.error);applied.fonts=args.fonts.map((f)=>f.role)}if(args.typeset){let r=await setTypeset2(ctx,args.typeset,args.theme);if(!r.ok)return errorResult(r.error);applied.typeset=args.typeset.map((t)=>t.name??"default")}if(args.customCss!==void 0){let r=await setCustomCss2(ctx,args.customCss);if(!r.ok)return errorResult(r.error);applied.customCss={bytes:r.value.bytes}}return jsonResult({theme:args.theme??"default",applied})}),mcp.registerTool("add_theme",{description:"Create a named theme (theme/<name>.json) by cloning an existing one. Boards pin it via update_board { patch: { theme } }; renders accept theme:. Edit it with set_theme's own theme param.",inputSchema:{name:string().describe("Lowercase kebab, not 'default'"),from:string().optional().describe("Source theme to clone; default 'default'"),overwrite:boolean().optional()}},async(args)=>toMcp(await addTheme(ctx,args.name,args.from,args.overwrite??!1))),mcp.registerTool("update_theme",{description:"Rename a named theme, repointing every board that pinned it (the response lists them). To change a theme's *tokens*, use set_theme with its `theme` param.",inputSchema:{name:string().describe("Theme to rename"),renameTo:string().describe("New name \u2014 lowercase kebab, not 'default'")}},async(args)=>toMcp(await renameTheme(ctx,args.name,args.renameTo))),mcp.registerTool("remove_theme",{description:"Delete a named theme. Refuses while any board still pins it, naming those boards \u2014 repoint or unpin them first with update_board { patch: { theme } }. The default theme cannot be removed.",inputSchema:{name:string()}},async(args)=>toMcp(await removeTheme(ctx,args.name))),mcp.registerTool("list_themes",{description:"List named themes and which boards use each.",inputSchema:{}},async()=>jsonResult(listThemes(ctx))),mcp.registerTool("import_theme",{description:"Code-to-design: seed the theme from an existing app's stylesheet instead of picking colors by hand \u2014 semantic slots, the raw `palette.*` passthrough for brand vars, fonts, radius, and the nearby tailwind.config's `theme.extend`. Dry-run by default; pass `apply: true` to persist. Run this BEFORE porting screens. Guide: velloo://guide/theme.",inputSchema:{css:string().optional().describe("Stylesheet text (use this OR cssPath)"),cssPath:string().optional().describe("Path to the stylesheet \u2014 absolute, or relative to the HOST APP root (where globals.css lives, normally OUTSIDE the design folder); the design folder is tried as a last resort"),theme:string().optional().describe('Named theme to merge into; default "default"'),apply:boolean().optional().describe("Persist the merge (default false = dry-run)"),tailwindConfigPath:string().optional().describe("Path to the app's tailwind.config (absolute, or relative to the design folder). Auto-detected near `cssPath` when omitted; its `theme.extend` (colors, boxShadow, fontFamily) is ingested and its `container` reported as guidance.")}},async(args)=>{let css=args.css?.trim()?args.css:void 0,cssResolvedPath;if(css===void 0){if(args.cssPath===void 0)return errorResult({kind:"BadRequest",message:"pass either `css` text or a `cssPath`"});let hostRoot=ctx.folder.config.hostApp?.root,bases=isAbsolute6(args.cssPath)?[""]:[...hostRoot?[hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp)]:[],join34(ctx.folder.root,".."),ctx.folder.root],tried=[];for(let base of bases){let candidate=base===""?args.cssPath:join34(base,args.cssPath);tried.push(candidate);let text=await readFile14(candidate,"utf8").catch(()=>null);if(text!==null){css=text,cssResolvedPath=candidate;break}}if(css===void 0)return errorResult({kind:"BadRequest",message:`could not read "${args.cssPath}" \u2014 tried ${tried.map((t)=>`"${t}"`).join(", ")}. The host app's globals.css usually lives OUTSIDE the design folder (the design folder is at <appRoot>/velloo). Pass an absolute path, a path relative to the host app root, or paste the stylesheet text directly as \`css\`.`})}let tailwindConfig=await readTailwindConfig(args.tailwindConfigPath,cssResolvedPath,ctx.folder.root),r=await importThemeCss2(ctx,css,{...args.theme!==void 0?{themeName:args.theme}:{},...args.apply!==void 0?{apply:args.apply}:{},...tailwindConfig!==null?{tailwindConfig}:{}});if(!r.ok)return errorResult(r.error);let{changes,coverage,warnings,applied}=r.value,container=detectContainer(tailwindConfig),detectedChartLibs=await detectChartLibs(ctx.folder,cssResolvedPath);return jsonResult({applied,changeCount:changes.length,coverage:{...coverage,summary:`${coverage.semantic} of ${coverage.semanticTotal} semantic slots mapped; ${coverage.palette} palette passthrough`},changes,warnings,...container?{container}:{},...detectedChartLibs.length>0?{detectedChartLibs,chartHint:`this app uses a client chart library \u2014 register its chart components as render:"live" extensions (add_extension) so the canvas preview AND emitted code use the app's real charts; the built-in Chart previews via echarts and will not pixel-match.`}:{},...applied?{}:{note:"dry-run \u2014 pass apply: true to persist these changes"}})}),mcp.registerTool("score_theme_contrast",{description:"Score WCAG contrast for a theme's salient color pairs in both palettes \u2014 ratio + tier (AAA / AA / AAlarge / Fail) per pair. Run it after reseeding a palette or tuning dark tokens.",inputSchema:{mode:_enum(["light","dark"]).optional().describe("Score only this palette; default both"),theme:string().optional().describe('Named theme to score; default "default"')}},async(args)=>{let resolved=resolveNamedTheme(ctx.folder,args.theme);if(!resolved.ok)return errorResult({kind:"BadRequest",message:resolved.message});let results=args.mode?scoreThemeContrast(resolved.theme,args.mode):scoreThemeContrastBoth(resolved.theme),fails=results.filter((r)=>r.tier==="Fail").length,passes=results.length-fails;return jsonResult({theme:args.theme??"default",summary:{total:results.length,passes,fails},results})})}import{randomUUID as randomUUID3}from"crypto";import{appendFileSync,mkdirSync,writeFileSync}from"fs";import{join as join35}from"path";var TRACE_ENV="VELLOO_TRACE",MAX_PARAM_STR=16384;function traceEnabled(){let v=process.env[TRACE_ENV];return v!==void 0&&v!==""&&v!=="0"&&v.toLowerCase()!=="false"}function serializeError(err){if(err instanceof Error){let out={message:err.message};if(err.name)out.name=err.name;if(err.stack)out.stack=err.stack;return out}return{message:String(err)}}var SECRET_KEY=/^(cookies?|localstorage|storagestate|storagestatepath|authorization|auth|token|accesstoken|refreshtoken|apikey|api_key|password|secret|credentials?|sessionstate)$/i;function redactSecrets(value){if(Array.isArray(value))return value.map(redactSecrets);if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))out[k]=SECRET_KEY.test(k)?"<redacted>":redactSecrets(v);return out}return value}function clampStrings(value,max){if(typeof value==="string")return value.length>max?`${value.slice(0,max)}\u2026<elided ${value.length-max} chars>`:value;if(Array.isArray(value))return value.map((v)=>clampStrings(v,max));if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))out[k]=clampStrings(v,max);return out}return value}function extForMime(mime){if(mime.includes("jpeg")||mime.includes("jpg"))return"jpg";if(mime.includes("webp"))return"webp";if(mime.includes("gif"))return"gif";return"png"}class TraceRecorder{dir;seq=0;broken=!1;startedAt=new Map;pending=new Map;assetsDir;constructor(dir){this.dir=dir;this.assetsDir=join35(dir,"assets")}start(input){if(this.broken)return-1;let seq=this.seq++,call={seq,ts:new Date().toISOString(),tool:input.tool,params:clampStrings(redactSecrets(input.params),MAX_PARAM_STR)};if(input.sessionId)call.sessionId=input.sessionId;return this.pending.set(seq,call),this.startedAt.set(seq,Date.now()),this.writePending(),seq}end(seq,outcome){if(this.broken||seq<0)return;let call=this.pending.get(seq),startedAt=this.startedAt.get(seq);this.pending.delete(seq),this.startedAt.delete(seq),this.write(seq,{tool:call?.tool??"?",params:call?.params??null,durationMs:startedAt!==void 0?Date.now()-startedAt:0,...call?.sessionId?{sessionId:call.sessionId}:{},result:outcome.result,error:outcome.error}),this.writePending()}record(input){if(this.broken)return;this.write(this.seq++,{...input,params:redactSecrets(input.params)})}write(seq,input){if(this.broken)return;try{let rec=this.build(seq,input);appendFileSync(join35(this.dir,"tape.jsonl"),`${JSON.stringify(rec)}
|
|
441
|
-
`)}catch(err){this.broken=!0,console.error(`velloo trace: recording disabled after write failure: ${String(err)}`)}}writePending(){if(this.broken)return;try{writeFileSync(
|
|
442
|
-
`):void 0,images,isError}}writeImage(seq,idx,item){if(typeof item.data!=="string")return null;let mimeType=typeof item.mimeType==="string"?item.mimeType:"image/png",buf=Buffer.from(item.data,"base64"),file=`assets/${seq}-${idx}.${extForMime(mimeType)}`;return mkdirSync(this.assetsDir,{recursive:!0}),writeFileSync(
|
|
443
|
-
`),console.error(`velloo trace: recording MCP session to ${dir}`),new TraceRecorder(dir)}catch(err){return console.error(`velloo trace: could not start recorder: ${String(err)}`),null}}function withCallRecording(mcp,recorder){let original=mcp.registerTool.bind(mcp),patched=(name,config,cb)=>{let handler=cb;return original(name,config,async(args,extra)=>{let sessionId=extra?.sessionId,seq=recorder.start({tool:name,params:args,sessionId});try{let result=await handler(args,extra);return recorder.end(seq,{result}),result}catch(err){throw recorder.end(seq,{error:err}),err}})};return mcp.registerTool=patched,mcp}var INSTRUCTION_PARTS=["You are working on a Velloo design folder: a code-shaped design canvas whose components are the project's real component library. Designs are static \u2014 click handlers, routing and forms are no-ops.","","**The design folder is tool-owned.** Screens, boards, snippets and the theme live as JSON files inside it, but never read or edit those files by hand \u2014 every operation goes through these tools, which hold the write lock, validation and history. The user can watch the design render live with `velloo run`.","",'**Start by reading, once.** `list_components` (one namespace for components, extensions, and snippets \u2014 the default index groups families by what they are for and carries usage notes; `filter` to a family for its prop names, `mode: "full"` for examples), `component_status` for exact/adapted/fallback canvas fidelity when it matters, `get_theme` for the palette and tokens, `list_boards` for boards and frames. For an existing screen, `get_screen mode: "outline"` before pulling the full JSON.',"","**Use the library's own components \u2014 the catalog is larger than you expect.** Before building a pattern out of `Box` + `Text`, check the index for a family that already is it: a labelled input with help and error text is `Field`, a search box with an inline icon is `InputGroup`, a settings/file row is `Item`, a \"nothing here yet\" panel is `Empty`, joined buttons are `ButtonGroup`. A family listing `pieces` is composed of them (`Field` \u21D2 `FieldLabel` / `FieldDescription` / `FieldError`). Hand-rolling one of these loses the library's spacing, states and dark-mode behavior, and `emit_code` then hands the developer a div stack instead of the component their app already has.","","**Three customization layers** stack additively:"," - **Libraries** are the baseline component palette. A folder registers N (`config.libraries`); each screen pins one via `screen.library`, and component ids resolve against that library only."," - **Extensions** add wholly new components the library doesn't have \u2014 the app's own `DataTable`, a brand `Hero`. Register with `add_extension`; they emit a real import. Additive customization, NOT compositions."," - **Snippets** compose existing components into named subtrees with typed params \u2014 the tool for repeated structure (FeatureCard, NavRow, PricingTier). `compose` resolves their PascalCase names in the same JSX tag namespace as components and extensions.","","**Styling is framework-native.** `update_props`'s `style` channel routes your payload to whatever the screen's framework uses \u2014 a Tailwind `className` string, an `sx` object, or a plain `style` object \u2014 so one verb works everywhere. Prefer theme tokens over hard-coded values in any channel; on a Tailwind folder prefer semantic tokens (`bg-background`, `text-muted-foreground`, `bg-primary`) over raw palette colors, because only semantic tokens theme-flip in dark mode.","","**EFFICIENCY \u2014 build in big strokes, read once.** A screen should take a few dozen tool calls, not hundreds; over-calling is the most common failure. (1) **Compose whole subtrees** \u2014 `compose` accepts familiar nested JSX, so build a whole feature card in ONE call rather than node-by-node. (2) **`batch` groups a sequence of metadata/property mutations into one round-trip**, atomic by default: on the first error every touched resource rolls back and the result reports `rolledBack: true` with the failing call. (3) **Work from memory** \u2014 do NOT re-`get_screen` or re-`list_boards` before every edit; to re-locate a node use `find_nodes`, which returns its path. (4) **Don't thrash** \u2014 plan the structure before building it, and edit a snippet through `update_snippet`'s `innerPatch` rather than redefining its body.","",'**Think in ids, not paths.** Anywhere a tool asks for a `path` (or `parentPath`, `fromPath`, `toParent`), pass a stable id reference like `"@hero-cta"`. Assign ids at creation (`id: "hero-cta"`) for anything you might touch again. Number paths are positional and break when siblings move; treat them as an implementation detail you get from `find_nodes` (`set_node_id` retrofits one).',"",'**Verify before declaring done.** Mutations return focused class, theme, and render diagnostics \u2014 a `render/component-threw` diagnostic at some path means the canvas shows a placeholder there instead of the component you asked for, and the message names what it needs (usually a parent it must sit inside); `screenshot mode: "compare"` renders light and dark side by side and returns a full-screen diagnostic pass. `emit_code` repeats that full check at the handoff boundary. See velloo://guide/verification.',"","**Make it distinctive.** Default library + Inter + one indigo reads as template. Set a display face and a typeset early via `set_theme` \u2014 one call re-proportions every screen \u2014 then reach for real art and confident color. See velloo://guide/art.","","**Read the guide before doing the thing.** The advertised `velloo://guide/*` resources carry the detail this brief deliberately omits; fetch the relevant one before using an unfamiliar capability."],GUIDED_INSTRUCTION_PARTS=["You are working on a Velloo design folder: a code-shaped design canvas backed by the project's real component library.","","**The design folder is tool-owned.** Never read or edit its JSON files by hand. Use `operation_schema` before an unfamiliar operation, `call_velloo` for one native operation, and `run_velloo_plan` for up to eight related calls.","","Build in large strokes with `compose` and `batch`, keep stable node ids, prefer theme tokens, and avoid repeatedly re-reading unchanged state. Use `component_status` before claiming an app component renders exactly. Verify design work with `screenshot`; use `compare_to_url` for code-to-design fidelity and `emit_code` at implementation handoff.","","The operation enum is the whole catalogue available to this session. Failed fa\xE7ade calls include the exact native operation schema needed to correct them.","","**Read the guide before doing the thing.** The `velloo://guide/*` resources carry the detail this brief omits \u2014 verification, theming, porting, art direction, comment threads. Fetch the relevant one before using an unfamiliar capability."],FEEDBACK_INSTRUCTION="**Sending product feedback**: this folder opted into the `send_feedback` tool. Reach for it when you hit friction with **Velloo itself** \u2014 a confusing instruction, a missing capability, a tool that misbehaved, a bug \u2014 or when the user asks to send feedback. ALWAYS show the user the exact `body` and get their go-ahead before calling; never send unprompted, even when you originated the idea. Fire sparingly \u2014 one report per distinct issue, never repeated. NEVER include the user's design content, code, or file/repo paths; describe the issue in your own words. This is feedback about Velloo, not about the design.";function buildInstructions(feedbackEnabled,canvasUrl,intro=[],openComments=0,hostTailwindMajor=null,bareFolder=!1,surface={mode:"full"}){let parts=[...intro,...surface.mode==="guided"?GUIDED_INSTRUCTION_PARTS:INSTRUCTION_PARTS];if(bareFolder)parts.unshift("**Bare folder.** This design has no boards yet. Setup order before composing UI: (1) style the theme with `set_theme` (or `import_theme` to match an existing app); (2) `add_board`; (3) add screens and frames, then design.","");if(hostTailwindMajor===3)parts.push("","**The host app is on Tailwind v3** (the canvas itself always compiles v4). Prefer classes spelled the same in both majors; avoid v4-only utilities (`inset-shadow-*`, `text-shadow-*`, `bg-linear-*` angles, container-query variants, `starting:`). Mutations flag incompatible classes at their paths, and `emit_code` returns a `tailwindV3Compat` rename list (e.g. v4 `shadow-sm` \u21D2 v3 `shadow`) to apply when writing app code. `emit_theme` detects the v3 target and emits `velloo-theme.css` + a `velloo.preset` instead of a v4 globals.css.");if(canvasUrl)parts.push("",`**The live canvas** is running at ${canvasUrl} \u2014 give the user this URL up front so they can open it and watch your edits render in real time. (They can also open a canvas any time with \`velloo run\`.)`);if(openComments>0){let one=openComments===1;parts.push("",`**${one?"1 open visual feedback thread is":`${openComments} open visual feedback threads are`} waiting on you.** ${one?"It is a change":"Each one is a change"} the user is expecting. Read ${one?"it":"them"} with the \`list_comment_threads\` operation, make the requested ${one?"change":"changes"}, then reply and resolve with \`update_comment_thread\`. The full loop is velloo://guide/comments.`)}if(feedbackEnabled)parts.push("",FEEDBACK_INSTRUCTION);return parts.join(`
|
|
444
|
-
`)}function buildMcpServer(ctx,jit,bundler,canvasBundler,comments,assetOrigin,cloud,surface=DEFAULT_MCP_SURFACE){let feedbackEnabled=Boolean(ctx.folder.config.feedback?.enabled&&cloud?.url),channel=styleChannelOf(ctx.defaultProvider,ctx.folder.config.styling?.framework),channelKind=channel.kind,intro=ctx.defaultProvider.mcpIntro?.(channelKind)??[],hostTailwindMajor=channel.needsTailwindJit?detectTailwindMajor(hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp)):null,openComments=comments.countOpenSync(),bareFolder=ctx.folder.boards.size===0,mcp=new McpServer(MCP_SERVER_INFO,{instructions:buildInstructions(feedbackEnabled,assetOrigin&&trimTrailingSlashes(assetOrigin),intro,openComments,hostTailwindMajor,bareFolder,surface)}),toolSurface=applyMcpToolSurface(mcp,surface);applyToolPolicy(mcp);let recorder=createTraceRecorder(ctx.folder.root);if(recorder)withCallRecording(mcp,recorder);if(registerDiscoveryTools(mcp,ctx),registerComposeTool(mcp,ctx,jit),registerMutationTools(mcp,ctx,jit),registerInspectTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerThemeTools(mcp,ctx),registerEmitTools(mcp,ctx,jit),registerScreenshotTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerExtensionTools(mcp,ctx),registerNoteTools(mcp,ctx),registerAssetTools(mcp,ctx),registerBatchTool(mcp,ctx,jit),registerCaptureTools(mcp,ctx),feedbackEnabled&&cloud)registerFeedbackTool(mcp,ctx,cloud);return registerCommentTools(mcp,comments),registerGenerateTools(mcp,ctx,cloud??{url:""}),toolSurface.finish(),registerGuideResources(mcp),mcp}async function readBody2(req){let chunks=[];for await(let chunk of req)chunks.push(chunk);if(chunks.length===0)return;let raw=Buffer.concat(chunks).toString("utf8");if(raw.length===0)return;return JSON.parse(raw)}function isInitializeRequest2(body){if(!body||typeof body!=="object")return!1;return(Array.isArray(body)?body:[body]).some((m)=>m&&typeof m==="object"&&m.method==="initialize")}function trimTrailingSlashes(url){let end=url.length;while(end>0&&url[end-1]==="/")end--;return url.slice(0,end)}function sendJson(res,status,value){res.statusCode=status,res.setHeader("Content-Type","application/json"),res.end(JSON.stringify(value))}async function createMcpServer(ctx,opts){let sessions=new Map,httpServer=createHttpServer(async(req,res)=>{if(!requestIsLocal({host:req.headers.host,origin:req.headers.origin})){sendJson(res,403,{error:"forbidden: request is not from a local origin"});return}if(!req.url?.startsWith("/mcp")){sendJson(res,404,{error:"expected /mcp"});return}let sessionId=req.headers["mcp-session-id"],sessionIdStr=Array.isArray(sessionId)?sessionId[0]:sessionId;try{if(req.method==="POST"){let body=await readBody2(req),session=sessionIdStr?sessions.get(sessionIdStr):void 0;if(!session){if(!isInitializeRequest2(body)){sendJson(res,400,{error:"missing or unknown mcp-session-id; send `initialize` first"});return}let transport=new StreamableHTTPServerTransport({sessionIdGenerator:()=>randomUUID4(),enableJsonResponse:!0,onsessioninitialized:(id)=>{sessions.set(id,{transport,server})}}),parsedSurface=parseMcpSurfaceUrl(req.url);if(!parsedSurface.ok){sendJson(res,400,{error:parsedSurface.error});return}let server=buildMcpServer(ctx,opts.jit,opts.bundler,opts.canvasBundler,opts.comments,opts.assetOrigin,opts.cloud,parsedSurface.selection);transport.onclose=()=>{if(transport.sessionId)sessions.delete(transport.sessionId);server.close().catch(()=>{return})},await server.connect(transport),session={transport,server}}let active=session;await withActor({source:"mcp",session:sessionIdStr??active.transport.sessionId},()=>active.transport.handleRequest(req,res,body));return}if(req.method==="GET"||req.method==="DELETE"){if(!sessionIdStr||!sessions.has(sessionIdStr)){sendJson(res,400,{error:"missing or unknown mcp-session-id"});return}let session=sessions.get(sessionIdStr);if(!session){sendJson(res,400,{error:"session lost"});return}await session.transport.handleRequest(req,res);return}sendJson(res,405,{error:`method ${req.method} not allowed`})}catch(err){if(console.error("velloo mcp: handler failed:",err),!res.headersSent)sendJson(res,500,{error:"internal error"})}});await new Promise((resolve,reject)=>{httpServer.once("error",reject),httpServer.listen(opts.port,opts.host,()=>{httpServer.removeListener("error",reject),resolve()})});let addr=httpServer.address(),boundPort=typeof addr==="object"&&addr!==null&&"port"in addr?addr.port:opts.port;return{url:`http://${opts.host}:${boundPort}/mcp`,port:boundPort,sessions:()=>sessions.size,async close(){for(let session of sessions.values())await session.transport.close().catch(()=>{return}),await session.server.close().catch(()=>{return});sessions.clear(),await new Promise((resolve)=>httpServer.close(()=>resolve()))}}}async function createStdioMcpServer(ctx,opts){let server=buildMcpServer(ctx,opts.jit,opts.bundler,opts.canvasBundler,opts.comments,opts.assetOrigin,opts.cloud,opts.surface??DEFAULT_MCP_SURFACE),transport=new StdioServerTransport;return await server.connect(transport),{async close(){await transport.close().catch(()=>{return}),await server.close().catch(()=>{return})}}}class PublishRunner{publisher;host;current={state:"idle"};warnings=[];constructor(publisher,host){this.publisher=publisher;this.host=host}state(){return this.current}running(){return this.current.state==="running"}teams(){return this.publisher.teams()}destinations(){return this.publisher.destinations(this.host())}access(){return this.publisher.access()}published(){return this.publisher.published()}unpublish(slug){return this.publisher.unpublish(slug)}start(request){if(this.current.state==="running")return null;let startedAt=new Date().toISOString();return this.warnings=[],this.current={state:"running",step:"start",message:"starting",startedAt,warnings:this.warnings},this.publisher.run(this.host(),request,({step,message,capture})=>{if(this.current.state!=="running"||this.current.startedAt!==startedAt)return;this.current={state:"running",step,message,startedAt,warnings:this.warnings,...capture?{capture}:{}}},(message)=>{this.warnings.push(message)}).then((result)=>{this.current={state:"done",result,warnings:this.warnings,finishedAt:new Date().toISOString()}}).catch((err)=>{let access=asSignInRequired(err),boardLimit=asBoardLimit(err);this.current={state:"error",message:err instanceof Error?err.message:String(err),...access?{signInRequired:access}:{},...boardLimit?{boardLimit}:{},warnings:this.warnings,finishedAt:new Date().toISOString()}}),this.current}reset(){if(this.current.state!=="running")this.current={state:"idle"}}}import{existsSync as existsSync8,readdirSync as readdirSync2,statSync,watch}from"fs";import{join as join36,sep as sep2}from"path";function classifyWatchPath(filename){if(!filename)return null;let parts=filename.split(sep2);if(parts[0]==="screens"&&parts[1]){let file=parts[1];if(file.endsWith(".annotations.json"))return{type:"annotations-changed",screenId:file.slice(0,-17)};if(file.endsWith(".json")){let stem=file.slice(0,-5);if(!stem.includes("."))return{type:"screen-changed",screenId:stem}}}if(parts[0]==="boards"&&parts[1]){let file=parts[1];if(file.endsWith(".notes.json"))return{type:"notes-changed",boardId:file.slice(0,-11)};if(file.endsWith(".json")){let stem=file.slice(0,-5);if(!stem.includes("."))return{type:"board-changed",boardId:stem}}}if(parts[0]==="theme"&&parts[1]&&/\.(json|css)$/.test(parts[1]))return{type:"theme-changed"};if(parts[0]==="snippets"&&parts[1]&&parts[1].endsWith(".json"))return{type:"snippet-changed",snippetId:parts[1].slice(0,-5)};return null}function watchDesignFolder(root,onEvent,debounceMs=50){let pending=new Map,watchers=[],fingerprint=designFingerprint(root);function schedule(key,ev){let prev=pending.get(key);if(prev)clearTimeout(prev);pending.set(key,setTimeout(()=>{pending.delete(key),onEvent(ev)},debounceMs))}for(let sub of["screens","boards","theme","snippets"])try{let w=watch(join36(root,sub),(_eventType,filename)=>{if(!filename)return;let rel=`${sub}${sep2}${filename}`,ev=classifyWatchPath(rel);if(ev)fingerprint=designFingerprint(root),schedule(rel,ev)});watchers.push(w)}catch{}let poll=setInterval(()=>{let next=designFingerprint(root),paths=new Set([...fingerprint.keys(),...next.keys()]);for(let path of paths){if(fingerprint.get(path)===next.get(path))continue;let event=classifyWatchPath(path);if(event)schedule(path,event)}fingerprint=next},250);return{close(){for(let w of watchers)w.close();clearInterval(poll);for(let t of pending.values())clearTimeout(t);pending.clear()}}}function designFingerprint(root){let out=new Map;for(let sub of["screens","boards","theme","snippets"]){let dir=join36(root,sub);if(!existsSync8(dir))continue;for(let name of readdirSync2(dir)){let path=join36(dir,name);try{let stat=statSync(path);if(stat.isFile())out.set(`${sub}${sep2}${name}`,`${stat.mtimeMs}:${stat.size}`)}catch{}}}return out}function watchSourcePaths(paths,onChange,debounceMs=80){let targets=[...new Set(paths)],watchers=new Map,timer,schedule=()=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>{timer=void 0,onChange()},debounceMs)},arm=(target)=>{if(watchers.has(target))return;try{if(!statSync(target).isDirectory())return;watchers.set(target,watch(target,{recursive:!0},(_event,filename)=>{if(filename)schedule()}))}catch{}},disarm=(target)=>{watchers.get(target)?.close(),watchers.delete(target)};for(let target of targets)arm(target);let fingerprint=sourceFingerprint(targets),poll=setInterval(()=>{let next=sourceFingerprint(targets);if(next===fingerprint)return;fingerprint=next;for(let target of targets)if(existsSync8(target))arm(target);else disarm(target);schedule()},250);return{close(){for(let watcher of watchers.values())watcher.close();if(watchers.clear(),clearInterval(poll),timer)clearTimeout(timer)}}}function sourceFingerprint(paths){let rows=[],visit=(path)=>{try{if(!existsSync8(path)){rows.push(`${path}:missing`);return}let stat=statSync(path);if(rows.push(`${path}:${stat.mtimeMs}:${stat.size}`),!stat.isDirectory())return;for(let name of readdirSync2(path).sort())visit(join36(path,name))}catch{rows.push(`${path}:raced`)}};for(let path of paths)visit(path);return rows.join("|")}var MIME2={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".woff2":"font/woff2",".ico":"image/x-icon"};async function serveStatic(req,canvasDist){let url=new URL(req.url),requested=url.pathname==="/"?"/index.html":url.pathname,fsPath=join37(canvasDist,requested);if(!(fsPath===canvasDist||fsPath.startsWith(canvasDist+sep3))||!existsSync9(fsPath))return null;let file=Bun.file(fsPath);if(!await file.exists())return null;let type=MIME2[extname3(fsPath)]??"application/octet-stream";return new Response(file,{headers:{"Content-Type":type}})}function extraThemeBlock(folder){let fonts={},palette={},spacing={},shadows={},animation={},keyframes={};for(let theme of folder.themes.values()){for(let[role,stack]of Object.entries(theme.typography.fontFamily??{}))fonts[role]=stack;for(let[name,value]of Object.entries(theme.palette??{}))palette[name]=value;for(let[name,value]of Object.entries(theme.paletteDark??{}))palette[name]??=value;for(let[name,value]of Object.entries(theme.spacing??{})){if(!Number.isNaN(Number(name)))continue;if(typeof value==="string"||typeof value==="number")spacing[name]=String(value)}for(let[name,value]of Object.entries(theme.shadows??{}))if(typeof value==="string"||typeof value==="number")shadows[name]=String(value);for(let[name,value]of Object.entries(theme.animation??{}))animation[name]=value;for(let[name,steps]of Object.entries(theme.keyframes??{}))keyframes[name]=steps}let lines=[...Object.entries(fonts).filter(([role])=>isCssIdent(role)).map(([role,stack])=>` --font-${role}: ${sanitizeCssTokenValue(stack)};`),...Object.entries(palette).map(([name,value])=>` --color-${name}: ${value};`),...Object.entries(spacing).map(([name,value])=>` --spacing-${name}: ${value};`),...Object.entries(shadows).map(([name,value])=>` --shadow-${name}: ${value};`),...Object.entries(animation).map(([name,value])=>` --animate-${name}: ${value};`),...typesetThemeTokens()],keyframesCss=keyframesToCss(Object.keys(keyframes).length>0?keyframes:void 0," "),safelist=typesetSafelist().map((pattern)=>`@source inline("${pattern}");`).join(`
|
|
440
|
+
`);return{message,offset,line:lines.length,column:(lines.at(-1)?.length??0)+1}}function wrapMixedText(children,ctx){let hasElement=children.some((child)=>("tag"in child)),hasText=children.some((child)=>!("tag"in child)&&child.text.trim().length>0);if(!hasElement||!hasText||!ctx.components.has("Text"))return children;return children.flatMap((child)=>{if("tag"in child)return[child];if(child.text.trim().length===0)return[];return[{tag:"Text",attributes:[],children:[child],offset:child.offset}]})}function textValue(children){let elements=children.filter((child)=>("tag"in child)),text=children.filter((child)=>("text"in child)).map((child)=>child.text).join("").replace(/\s+/g," ").trim();return{...text?{text}:{},elements}}function snippetJsxTags(snippet){let pascal=(value)=>value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part)=>`${part[0]?.toUpperCase()??""}${part.slice(1)}`).join("");return[...new Set([snippet.id,snippet.name].map(pascal).filter(Boolean))]}function compileElement(element,ctx){if(element.tag===null){let meaningful=element.children.filter((child)=>("tag"in child)||child.text.trim().length>0);if(meaningful.length!==1||!("tag"in(meaningful[0]??{})))return{ok:!1,issues:[issueAt(ctx.source,element.offset,"A fragment must contain exactly one root element")]};return compileElement(meaningful[0],ctx)}let attrs=new Map;for(let attr of element.attributes){if(attrs.has(attr.name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Duplicate attribute "${attr.name}"`)]};if(/^on[A-Z]/.test(attr.name)||["dangerouslySetInnerHTML","ref","key"].includes(attr.name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Executable React attribute "${attr.name}" is not allowed in static design JSX`)]};attrs.set(attr.name,attr)}let stableId=attrs.get("vellooId")?.value;if(stableId!==void 0&&typeof stableId!=="string")return{ok:!1,issues:[issueAt(ctx.source,attrs.get("vellooId")?.offset??element.offset,"vellooId must be a string")]};attrs.delete("vellooId");let snippetMatches=ctx.snippets.get(element.tag)??[],isComponent=ctx.components.has(element.tag);if(isComponent&&snippetMatches.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`"${element.tag}" is ambiguous: both a component and a snippet use this tag`)]};let{text,elements}=textValue(wrapMixedText(element.children,ctx));if(text&&elements.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,"Mixed text and element children are not supported; wrap the text in a Text or Box element")]};if(isComponent){let props=Object.fromEntries([...attrs].map(([name,attr])=>[name,attr.value]));if(text){if("children"in props)return{ok:!1,issues:[issueAt(ctx.source,element.offset,"Specify children as text or a prop, not both")]};props.children=text}let children=[];for(let child of elements){let compiled=compileElement(child,ctx);if(!compiled.ok)return compiled;children.push(compiled.node)}return{ok:!0,node:{$ref:element.tag,...stableId?{$id:stableId}:{},...Object.keys(props).length>0?{props}:{},...children.length>0?{children}:{}}}}if(snippetMatches.length===1){let snippet=snippetMatches[0],params=new Map(snippet.params.map((param)=>[param.name,param])),args={},extraClassName;for(let[name,attr]of attrs)if(name==="className"&&!params.has(name)){if(typeof attr.value!=="string")return{ok:!1,issues:[issueAt(ctx.source,attr.offset,"A snippet className must be a string")]};extraClassName=attr.value}else if(!params.has(name))return{ok:!1,issues:[issueAt(ctx.source,attr.offset,`Unknown prop "${name}" for snippet ${element.tag}`)]};else args[name]=attr.value;if(text||elements.length>0){let childParam=params.get("children");if(!childParam)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} declares no children param`)]};if(text)args.children=text;else{if(childParam.type!=="node"||elements.length!==1)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} needs one node child for its children param`)]};let compiled=compileElement(elements[0],ctx);if(!compiled.ok)return compiled;args.children=compiled.node}}let missing=snippet.params.filter((param)=>!param.optional&¶m.default===void 0&&!(param.name in args)).map((param)=>param.name);if(missing.length>0)return{ok:!1,issues:[issueAt(ctx.source,element.offset,`Snippet ${element.tag} is missing required props: ${missing.join(", ")}`)]};return{ok:!0,node:{$snippet:snippet.id,...stableId?{$id:stableId}:{},...extraClassName?{$extraClassName:extraClassName}:{},...Object.keys(args).length>0?{args}:{}}}}let allNames=[...ctx.components,...ctx.snippets.keys()],suggestions=nearestRefs(element.tag,allNames),catalogOnly=ctx.catalog.has(element.tag);return{ok:!1,issues:[issueAt(ctx.source,element.offset,catalogOnly?`Component "${element.tag}" is known to the library but unavailable in its design renderer; refresh the provider snapshot`:`Unknown component or snippet "${element.tag}"${suggestions.length?`; did you mean ${suggestions.join(", ")}?`:""}`)]}}async function compileRestrictedJsx(ctx,screen,source){let root;try{root=new Parser(source).parse()}catch(error){if(error instanceof ParseFailure)return{ok:!1,issues:[issueAt(source,error.offset,error.message)]};throw error}let registry=registryForScreen2(ctx,screen),manifest=await providerForScreen2(ctx,screen).loadManifest().catch(()=>[]),snippets=new Map;for(let snippet of ctx.folder.snippets.values())for(let tag of snippetJsxTags(snippet)){let values=snippets.get(tag)??[];values.push(snippet),snippets.set(tag,values)}return compileElement(root,{source,components:new Set(Object.keys(registry)),catalog:new Set(manifest.map((descriptor)=>descriptor.id)),snippets})}var ViewportArgSchema=preprocess((v)=>{if(typeof v!=="object"||v===null||Array.isArray(v))return v;let o=v;if(o.w!==void 0||o.h!==void 0)return v;let{width,height,...rest}=o;return width===void 0&&height===void 0?v:{...rest,w:width,h:height}},ViewportSchema),ThemeNameSchema=string().optional().describe("Named theme to render with (boards pin one)"),RenderModeSchema=_enum(["light","dark","compare"]).optional();function sourceError(message,issues){return errorResult({kind:"BadRequest",message,issues})}function registerComposeTool(mcp,ctx,jit){mcp.registerTool("compose",{description:"Append one subtree or replace a screen tree using safe restricted JSX. Tags resolve automatically across the screen's library, extensions, and PascalCase snippet names. Supports nested tags, literal text, quoted props, and JSON literals in braces; no JavaScript executes. Use `vellooId` for a stable @id. Errors include line/column. Missing host-app packages never block design and are reported later by `emit_code.componentsToInstall`.",inputSchema:{screenId:string(),mode:_enum(["append","replace"]),jsx:string().min(1),parentPath:LocatorSchema.optional().describe("append only; default [] (the root node)"),index:number().int().nonnegative().optional().describe("append only")}},async({screenId,mode,jsx,parentPath,index})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult({kind:"ScreenNotFound",screenId});if(mode==="replace"&&(parentPath!==void 0||index!==void 0))return sourceError("compose replace does not accept parentPath or index",[{message:"Remove append-only arguments",offset:0,line:1,column:1}]);let compiled=await compileRestrictedJsx(ctx,screen,jsx);if(!compiled.ok)return sourceError("compose could not compile the restricted JSX",compiled.issues);let mutation;if(mode==="replace")mutation=await setScreenTree2(ctx,{screenId,tree:compiled.node});else if(isComponentNode(compiled.node))mutation=await addNode2(ctx,{screenId,parentPath:parentPath??[],componentRef:compiled.node.$ref,...compiled.node.$id?{id:compiled.node.$id}:{},...compiled.node.props?{props:compiled.node.props}:{},...compiled.node.children?{children:compiled.node.children}:{},...index!==void 0?{index}:{}});else if(isSnippetInstance(compiled.node))mutation=await instantiateSnippet2(ctx,{screenId,parentPath:parentPath??[],snippetId:compiled.node.$snippet,...compiled.node.$id?{id:compiled.node.$id}:{},...compiled.node.args?{args:compiled.node.args}:{},...compiled.node.$extraClassName?{extraClassName:compiled.node.$extraClassName}:{},...index!==void 0?{index}:{}});else return sourceError("compose root must be a component or snippet",[{message:"Parameter references are only valid inside snippet definitions",offset:0,line:1,column:1}]);if(!mutation.ok)return toMcp(mutation);let resulting=ctx.folder.screens.get(screenId),[propWarnings,diagnostics]=await Promise.all([resulting&&isComponentNode(compiled.node)?propWarningsForTree(ctx,resulting,compiled.node).catch(()=>[]):[],resulting?diagnosticsForScreen(ctx,jit,resulting).catch(()=>[]):[]]);return jsonResult({mode,...mutation.value,root:isComponentNode(compiled.node)?{kind:"component",id:compiled.node.$ref}:isSnippetInstance(compiled.node)?{kind:"snippet",id:compiled.node.$snippet}:{kind:"unknown"},...propWarnings.length>0?{propWarnings}:{},...diagnostics.length>0?{diagnostics}:{}})})}import{existsSync as existsSync9,realpathSync as realpathSync5}from"fs";function registerDesignTools(mcp,ctx,designs){if(mcp.registerTool("list_designs",{description:"List this checkout's designs; `current` marks this session's.",inputSchema:{}},async()=>jsonResult({designs:await listDesigns(ctx.folder.root)})),!designs.switchable)return;mcp.registerTool("switch_design",{description:"Move this session to another design, by name.",inputSchema:{name:string().min(1)}},async({name})=>{let found=await switchTarget(ctx.folder.root,name);if(!found.ok)return errorResult(found.error);let here=existsSync9(ctx.folder.root)?realpathSync5(ctx.folder.root):ctx.folder.root,there=existsSync9(found.directive.root)?realpathSync5(found.directive.root):found.directive.root;if(here===there)return jsonResult({kind:"DesignUnchanged",name});return{...jsonResult({kind:"DesignSwitchPending",name}),_meta:{[SWITCH_DESIGN_META]:found.directive}}})}import{readFile as readFile13}from"fs/promises";import{join as join32}from"path";var FoundNodeSchema=looseObject({path:array(number()),kind:_enum(["component","snippet","param"]),ref:string().optional(),id:string().optional(),className:string().optional(),textPreview:string().optional(),childCount:number()}),FindNodesOutput=looseObject({matches:array(FoundNodeSchema),total:number()}),ListComponentsOutput=looseObject({snapshotVersion:string(),components:array(looseObject({id:string(),kind:_enum(["library","extension","snippet"]),availableInDesign:boolean(),installedInApp:boolean(),snippetId:string().optional(),unused:boolean().optional(),importPath:string().optional()}))}),EmitSnippetIrSchema=looseObject({id:string(),componentName:string(),params:array(looseObject({name:string(),type:string(),default:string().optional(),optional:boolean().optional()})),jsx:string(),componentsToInstall:array(string()),helpersToMaterialize:array(string()),warnings:array(string())}),EmitCodeOutput=looseObject({screen:looseObject({id:string(),name:string()}),jsx:string(),componentsUsed:array(string()),iconsUsed:array(string()),snippetsUsed:array(EmitSnippetIrSchema),classesUsed:array(string()),componentsToInstall:array(string()),helpersToMaterialize:array(string()),warnings:array(string()),tailwindV3Compat:array(unknown()).optional()}),CompareToUrlOutput=looseObject({similarity:number(),changedRatio:number(),contentSimilarity:number().optional(),heightDelta:number(),bitmapHeightDelta:number().optional(),contentHeight:number(),topMismatches:array(string()).optional(),styleDiff:array(unknown()).optional(),regions:array(unknown()),framesShorterThanContent:array(unknown()).optional(),note:string().optional(),capture:looseObject({}).optional(),urlCache:looseObject({}).optional(),unverified:literal(!0).optional(),redirected:looseObject({}).optional(),authWall:literal(!0).optional(),pageError:string().optional(),warning:string().optional()});function withRequiredFlag(param){return{...param,required:param.default===void 0&&!param.optional}}function shortClass(cls,max=40){let trimmed=cls.trim();return trimmed.length>max?`${trimmed.slice(0,max-1)}\u2026`:trimmed}var MAX_ENUM_VALUES=40;function trimLargeEnums(entry){if(!entry.props.some((p)=>p.enumValues&&p.enumValues.length>MAX_ENUM_VALUES))return entry;let props=entry.props.map((p)=>p.enumValues&&p.enumValues.length>MAX_ENUM_VALUES?{...p,enumValues:p.enumValues.slice(0,MAX_ENUM_VALUES),enumValuesTruncated:p.enumValues.length-MAX_ENUM_VALUES}:p);return{...entry,props}}function nodeToOutline(node){if(isParamRef(node))return{param:node.$param};if(isSnippetInstance(node)){let out={snippet:node.$snippet},id=nodeId(node);if(id)out.$id=id;return out}if(!isComponentNode(node))return{};let out={ref:node.$ref},id=nodeId(node);if(id)out.$id=id;let cls=typeof node.props?.className==="string"?node.props.className:"";if(cls)out.classSnippet=shortClass(cls);if(node.children&&node.children.length>0)out.children=node.children.map(nodeToOutline);return out}function toOutline(screen){return{id:screen.id,name:screen.name,tree:nodeToOutline(screen.tree)}}async function loadManifestForCtx2(ctx){let onDisk=join32(ctx.folder.root,".design","manifest.json");try{let raw=await readFile13(onDisk,"utf8");return JSON.parse(raw)}catch{return ctx.defaultProvider.loadManifest()}}function toSummary(c){let out={id:c.id,category:c.category,source:c.source,props:c.props.map((p)=>p.name)};if(c.designModeNotes)out.designModeNotes=c.designModeNotes;return out}var EXTENSION_SHELF={id:"extensions",label:"Extensions (this app's own)"},SNIPPET_SHELF={id:"snippets",label:"Snippets (your compositions)"};function componentIndex(entries,groupOrder,labelFor){let byGroup=new Map;for(let entry of entries){let group=entry.group??"",familyId=entry.family??entry.id,families=byGroup.get(group)??new Map,family=families.get(familyId)??{id:familyId};if(entry.id!==familyId)family.pieces=[...family.pieces??[],entry.id];if(entry.designModeNotes)family.designModeNotes=entry.id===familyId?[entry.designModeNotes,family.designModeNotes].filter(Boolean).join(" "):[family.designModeNotes,`${entry.id}: ${entry.designModeNotes}`].filter(Boolean).join(" ");families.set(familyId,family),byGroup.set(group,families)}let order=[...groupOrder.map((g)=>g.id),""],groups=[...byGroup.entries()].sort(([a],[b])=>order.indexOf(a)-order.indexOf(b)).map(([group,families])=>({group:group===""?"other":group,label:labelFor(group===""?void 0:group),families:[...families.values()].map((family)=>family.pieces?{...family,pieces:family.pieces.sort()}:family).sort((a,b)=>a.id.localeCompare(b.id))})),index={groups,totals:{components:entries.length,families:groups.reduce((sum,g)=>sum+g.families.length,0)}},unavailable=entries.filter((e)=>!e.availableInDesign).map((e)=>e.id),uninstalled=entries.filter((e)=>!e.installedInApp).map((e)=>e.id);if(unavailable.length>0)index.unavailableInDesign=unavailable;if(uninstalled.length>0)index.notInstalledInApp=uninstalled;return index}function listBoardsPayload(folder,opts={}){let entries=opts.includeArchived?orderedBoards(folder):activeBoards(folder),groups=new Map((folder.config.boardGroups??[]).map((g)=>[g.id,g.name]));return entries.map(([id,board])=>({id,name:board.name,frameCount:board.frames.length,...board.group?{group:groups.get(board.group)??board.group}:{},...board.archivedAt?{archivedAt:board.archivedAt}:{},...opts.includeFrames?{frames:board.frames,groups:board.groups}:{}}))}function registerDiscoveryTools(mcp,ctx){mcp.registerTool("list_screens",{description:"List every screen in the design folder. Pass `includeTree: true` to embed each screen's full tree \u2014 one round-trip instead of list_screens + N get_screen calls. Default false to keep responses small.",inputSchema:{includeTree:boolean().optional()}},async({includeTree})=>{let screens=[...ctx.folder.screens.entries()].map(([id,screen])=>({id,name:screen.name,...includeTree?{tree:screen.tree}:{}}));return jsonResult({screens})}),mcp.registerTool("get_screen",{description:'Return one screen\'s JSON. `mode: "full"` (default) returns the complete tree; `"outline"` returns a stripped tree per node \u2014 {ref|snippet, $id?, classSnippet, children} \u2014 for an overview of a large screen before drilling in.',inputSchema:{screenId:string(),mode:_enum(["full","outline"]).optional()}},async({screenId,mode})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(screenNotFound(screenId));if(mode==="outline")return jsonResult(toOutline(screen));return jsonResult(screen)}),mcp.registerTool("list_boards",{description:"List the folder's live boards in sidebar order, each with its own frames. `group` names the sidebar group a board is filed under (absent \u21D2 ungrouped). `includeFrames: true` embeds each board's frame list; `includeArchived: true` also lists archived boards, which carry `archivedAt`.",inputSchema:{includeFrames:boolean().optional(),includeArchived:boolean().optional()}},async({includeFrames,includeArchived})=>jsonResult({boards:listBoardsPayload(ctx.folder,{includeFrames,includeArchived})})),mcp.registerTool("get_board",{description:"Return one board's full JSON \u2014 frames (placements of screens at chosen sizes) and groups.",inputSchema:{boardId:string()}},async({boardId})=>{let board=ctx.folder.boards.get(boardId);if(!board)return errorResult(boardNotFound(boardId));return jsonResult(board)}),mcp.registerTool("list_components",{description:'List the unified `compose` tag namespace: library components, extensions, and snippets. `mode: "index"` (default) groups families onto shelves with their sub-pieces folded in and carries per-family usage notes \u2014 read this first, then `filter` to the handful you will actually use. `"summary"` adds prop names per component; `"full"` returns whole descriptors with examples. A family entry means you compose its `pieces` inside it (`Field` \u21D2 `FieldLabel`/`FieldDescription`/`FieldError`) \u2014 prefer a real family over rebuilding one from `Box` and `Text`. Snippet entries carry `snippetId`; their `id` is the PascalCase JSX tag. `installedInApp` is host-app status only; false does not block design, and `emit_code.componentsToInstall` carries the handoff plan. `filter` substring-matches tags; `kind` narrows the result. A snippet no screen reaches \u2014 directly or through another snippet \u2014 is marked `unused`; `unusedOnly: true` lists just those, which is how you find snippets a board delete or a screen rewrite left behind.',outputSchema:ListComponentsOutput,inputSchema:{filter:string().optional(),mode:_enum(["index","summary","full"]).optional(),kind:_enum(["library","extension","snippet"]).optional(),unusedOnly:boolean().optional().describe('Only snippets nothing reaches \u2014 implies kind: "snippet"')}},async({filter,mode,kind,unusedOnly})=>{let manifest=await loadManifestForCtx2(ctx),provider=ctx.defaultProvider,catalog=provider.catalog?await provider.catalog().catch(()=>[]):[],catalogById=new Map(catalog.map((entry)=>[entry.id,entry])),libraryEntries=manifest.map((c)=>{let status=catalogById.get(c.id);return{...c,kind:"library",availableInDesign:c.id in provider.registry,...status?{installedInApp:status.installed,importPath:status.importPath}:{installedInApp:!0}}}),extensions=ctx.folder.config.extensions??{},extensionEntries=Object.entries(extensions).map(([id,ext])=>({id,category:ext.category??"ui",source:"extension",props:ext.props,designModeNotes:ext.description,kind:"extension",importPath:ext.importPath,availableInDesign:!0,installedInApp:!0})),unused=new Set(unusedSnippetIds(ctx.folder)),snippetEntries=[...ctx.folder.snippets.values()].map((snippet)=>({id:snippetJsxTags(snippet)[0]??snippet.id,snippetId:snippet.id,category:"composition",source:"snippet",props:snippet.params.map(withRequiredFlag),designModeNotes:unused.has(snippet.id)?`Reusable snippet: ${snippet.name} \u2014 unused: no screen reaches it`:`Reusable snippet: ${snippet.name}`,kind:"snippet",availableInDesign:!0,installedInApp:!0,...unused.has(snippet.id)?{unused:!0}:{}})),all=unusedOnly?snippetEntries.filter((entry)=>unused.has(entry.snippetId)):kind==="library"?libraryEntries:kind==="extension"?extensionEntries:kind==="snippet"?snippetEntries:[...libraryEntries,...extensionEntries,...snippetEntries],filtered=filter?all.filter((c)=>c.id.toLowerCase().includes(filter.toLowerCase())):all;if((mode??"index")==="index"){let order=[...COMPONENT_GROUPS,EXTENSION_SHELF,SNIPPET_SHELF],shelfOf=(group)=>order.find((g)=>g.id===group)?.label??UNGROUPED_LABEL,index=componentIndex(filtered.map((c)=>({id:c.id,group:c.kind==="extension"?EXTENSION_SHELF.id:c.kind==="snippet"?SNIPPET_SHELF.id:("group"in c)?c.group:void 0,family:"family"in c?c.family:void 0,designModeNotes:c.designModeNotes,availableInDesign:c.availableInDesign,installedInApp:c.installedInApp})),order,shelfOf);return structuredResult({snapshotVersion:ctx.defaultProvider.version,...index})}let out=mode==="full"?filtered.map((entry)=>entry.kind==="snippet"?entry:trimLargeEnums(entry)):filtered.map((c)=>({...c.kind==="snippet"?{id:c.id,category:c.category,source:c.source,props:c.props.map((prop)=>prop.name),designModeNotes:c.designModeNotes}:toSummary(c),kind:c.kind,availableInDesign:c.availableInDesign,installedInApp:c.installedInApp,...c.kind==="snippet"?{snippetId:c.snippetId,..."unused"in c?{unused:c.unused}:{}}:{},..."importPath"in c?{importPath:c.importPath}:{}}));return structuredResult({snapshotVersion:ctx.defaultProvider.version,components:out})}),mcp.registerTool("component_status",{description:'Report how components render: exact host source, canvas-adapted source, a real bundled framework adapter, fallback, or unavailable. Pass `screen` to check exactly the components a screen uses and whether it client-mounts host source. Or pass `ids` (e.g. { ids: ["Button", "Card"] }) \u2014 the same ids `list_components` returns. `renderable` describes whether Velloo can render it; `hostMount` is the separate host-app browser-mount capability.',inputSchema:{ids:array(string().min(1)).min(1).optional(),screen:string().min(1).optional().describe("Screen id: report the components it uses and whether it mounts"),library:string().min(1).optional().describe("With `ids`; a screen names its own")}},async({ids,screen:screenId,library})=>{if(screenId!==void 0){let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(screenNotFound(screenId));if(!ctx.canvasBundler)return errorResult({kind:"BadRequest",message:"component_status { screen } needs the canvas daemon's bundler."});let mount=await screenMount(ctx,ctx.canvasBundler,screen);if(mount.kind==="none"){let provider=providerForScreen2(ctx,screen),libraryId=libraryIdForScreen(ctx,screen);if(!provider.canvasBundleSpec){let manifest=await provider.loadManifest().catch(()=>[]),known=new Set(manifest.map((entry)=>entry.id)),refs=collectSerializedRefs(serializeTree(screen.tree,{snippets:ctx.folder.snippets})).filter((id)=>known.has(id));return jsonResult({library:libraryId,screen:screenId,renderable:!0,renderSource:"bundled-adapter",hostMount:{supported:!1,mounted:!1},note:"This screen renders through Velloo's bundled framework adapter using the real library runtime. Host-app client mounting is not supported by this adapter; that does not make its components unusable.",diagnostics:refs.map((id)=>({id,status:"bundled",note:"Rendered by the real library through Velloo's bundled adapter."})),errors:[]})}return jsonResult({screen:screenId,mounted:!1,note:"This screen's library renders server-side only, or the screen uses no components \u2014 there is no app mount to report on.",diagnostics:[],errors:[]})}return jsonResult({library:mount.libraryId,screen:screenId,mounted:mount.kind==="mounted",...mount.kind==="server"?{note:`The canvas and every capture render this screen from Velloo's bundled components, not the app's own, because ${mount.reason}. Statuses below describe each component's source; none of them reaches the screen until the blocking ones are fixed or replaced.`}:{},diagnostics:mount.bundle?.diagnostics??[],errors:mount.bundle?.errors??[]})}if(!ids)return errorResult({kind:"BadRequest",message:'Pass `screen` (a screen id) or `ids` (component ids), e.g. { screen: "home" }.'});let libraryId=library??ctx.folder.config.defaultLibrary,provider=ctx.providers[libraryId];if(!provider)return errorResult({kind:"BadRequest",message:`Unknown library ${JSON.stringify(libraryId)}.`});let manifest=await provider.loadManifest().catch(()=>[]),known=new Set(manifest.map((entry)=>entry.id)),unknownDiagnostics=ids.filter((id)=>!known.has(id)).map((id)=>({id,status:"unknown",note:"Not a component in this library \u2014 call list_components for the ids it accepts. An app component under its own name is not one of them; design with the library's components and match the app's styling."})),recognized=ids.filter((id)=>known.has(id));if(!provider.canvasBundleSpec||!ctx.canvasBundler)return jsonResult({library:libraryId,renderable:!0,renderSource:"bundled-adapter",hostMount:{supported:!1,mounted:!1},diagnostics:[...recognized.map((id)=>({id,status:"bundled",note:"Rendered by the real library through Velloo's bundled adapter; host-app client mounting is unavailable."})),...unknownDiagnostics],errors:[]});let result=recognized.length?await ctx.canvasBundler.build(libraryId,recognized):{usable:!1,diagnostics:[],errors:[]};return jsonResult({library:libraryId,usable:result.usable,diagnostics:[...result.diagnostics,...unknownDiagnostics],errors:result.errors})}),mcp.registerTool("get_theme",{description:"Return a theme token tree \u2014 the default, or a named one via `theme` \u2014 plus the folder's `customCss`. `typography.typesets` holds the rhythm controls and `typeScale` shows what they compute to per role. Adjust via `set_theme`, not per-node sizes.",inputSchema:{theme:string().optional().describe('Named theme to read; default "default"')}},async(args)=>{let resolved=resolveNamedTheme(ctx.folder,args.theme);if(!resolved.ok)return errorResult({kind:"BadRequest",message:resolved.message});let typography=resolved.theme.typography;return jsonResult({...resolved.theme,typeScale:typesetScale(typography.typesets?.[DEFAULT_TYPESET_NAME],{...typography.fontFamily?{fontFamily:typography.fontFamily}:{}}),customCss:ctx.folder.customCss})}),mcp.registerTool("get_snippet",{description:"Return the full JSON for a single snippet (id, name, params, body tree). Each param reports a derived `required` flag (no default and not optional).",inputSchema:{snippetId:string()}},async({snippetId})=>{let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return errorResult(snippetNotFound(snippetId));return jsonResult({...snippet,params:snippet.params.map(withRequiredFlag)})}),mcp.registerTool("list_annotations",{description:"List a screen's annotations \u2014 designer-authored guidance plus any you pinned. Each is anchored to a node and carries an `author`; `resolved` is the live path, or null when the targeted node has vanished (treat those as low-priority). User-authored ones are read-only to you. Bodies are markdown.",inputSchema:{screenId:string()}},async({screenId})=>{let screen=ctx.folder.screens.get(screenId),list=(ctx.folder.annotations.get(screenId)??[]).map((a)=>{let resolved=screen?resolveLocator(screen.tree,a.target.locator):null;return{...a,resolved}});return jsonResult({annotations:list})}),mcp.registerTool("list_notes",{description:"List the markdown notes on one board \u2014 commentary beside the frames or attached to a node (tour steps, review remarks, handoff context). An attached note carries `attachment` naming the frame, screen and node it anchors to.",inputSchema:{boardId:string()}},async({boardId})=>{let notes=ctx.folder.notes.get(boardId)??[];return jsonResult({notes})})}import{isAbsolute as isAbsolute4,resolve as resolve10,sep as sep2}from"path";function v3CompatFor(ctx,classes){let hostRoot=hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp);if(detectTailwindMajor(hostRoot)!==3)return[];return v3ClassIssues(classes)}async function targetFor(ctx,thing){let provider=providerForScreen2(ctx,thing);if(!provider.codegenModule)return;let manifest=await provider.loadManifest();return moduleTarget(manifest.filter((c)=>c.source!=="velloo").map((c)=>c.id),provider.codegenModule)}function isInlineStyle(ctx,thing){return styleChannelOf(providerForScreen2(ctx,thing),ctx.folder.config.styling?.framework).kind==="style"}function registerEmitTools(mcp,ctx,jit){mcp.registerTool("emit_code",{description:"Return agent-consumed IR for a screen plus full class/theme diagnostics: the JSX body in the screen framework's native idiom (Tailwind classes for shadcn, `sx={{\u2026}}` for MUI), plus the components, icons, snippets and classes used. **Not** a paste-ready file \u2014 no imports, no prettier pass. Read it and write the real code in the user's app conventions.",outputSchema:EmitCodeOutput,inputSchema:{screenId:string(),componentsAlias:string().optional()}},async(args)=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return errorResult(screenNotFound(args.screenId));let componentsAlias=args.componentsAlias??ctx.folder.config.codegen?.componentsAlias,target=await targetFor(ctx,screen),inlineStyle=isInlineStyle(ctx,screen),result=await emitCode(screen,{...componentsAlias?{componentsAlias}:{},snippets:ctx.folder.snippets,extensions:ctx.folder.config.extensions,...target?{target}:{},...inlineStyle?{inlineStyle:!0}:{}});if(!result.ok)return errorResult(result.error);let compat=target||inlineStyle?[]:v3CompatFor(ctx,[...result.value.classesUsed,...result.value.snippetsUsed.flatMap((s)=>classNamesInJsx(s.jsx))]),diagnostics=await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]);return structuredResult({...result.value,...compat.length>0?{tailwindV3Compat:compat}:{},...diagnostics.length>0?{diagnostics}:{}})}),mcp.registerTool("emit_snippet",{description:"Return agent-consumed IR for a single snippet: PascalCase component name, typed params, JSX body.",inputSchema:{snippetId:string(),componentsAlias:string().optional()}},async(args)=>{let snippet=ctx.folder.snippets.get(args.snippetId);if(!snippet)return errorResult(snippetNotFound(args.snippetId));let componentsAlias=args.componentsAlias??ctx.folder.config.codegen?.componentsAlias,target=await targetFor(ctx,snippet),inlineStyle=isInlineStyle(ctx,snippet),result=await emitSnippet(snippet,{...componentsAlias?{componentsAlias}:{},snippets:ctx.folder.snippets,extensions:ctx.folder.config.extensions,...target?{target}:{},...inlineStyle?{inlineStyle:!0}:{}});if(!result.ok)return errorResult(result.error);let compat=target||inlineStyle?[]:v3CompatFor(ctx,classNamesInJsx(result.value.jsx)),diagnostics=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);return structuredResult({...result.value,...compat.length>0?{tailwindV3Compat:compat}:{},...diagnostics.length>0?{diagnostics}:{}})}),mcp.registerTool("emit_theme",{description:"Write the active framework's theme artifact \u2014 shadcn \u21D2 Tailwind globals.css, native frameworks \u21D2 their own theme module \u2014 plus a framework-neutral DTCG `tokens.json`. Dry-run by default. These are finished artifacts, not IR: no agent translation, and the result's `notes` carry any one-time wiring steps. Guide: velloo://guide/theme.",inputSchema:{outputDir:string(),cssPath:string().optional().describe('shadcn: globals.css location relative to outputDir; default "app/globals.css"'),themePath:string().optional().describe('MUI: createTheme module location relative to outputDir; default "theme.ts"'),apply:boolean().optional(),cssOnly:boolean().optional(),theme:string().optional().describe("Named theme to emit; default 'default'"),tailwind:union([literal(3),literal(4)]).optional().describe("Force the Tailwind major of the emitted artifacts; default: detected from outputDir's package.json")}},async(args)=>{let out=resolve10(ctx.folder.root,args.outputDir);for(let rel of[args.cssPath,args.themePath]){if(rel===void 0)continue;if(isAbsolute4(rel)||!resolve10(out,rel).startsWith(out+sep2))return errorResult(`emit_theme: path must stay within outputDir (got ${rel})`)}let theme=themeByName(ctx.folder,args.theme),adapter=ctx.defaultProvider;if(adapter.themeToNative&&adapter.themeModule){let result=await emitNativeTheme(adapter.themeToNative(theme),{spec:adapter.themeModule,outputDir:out,...args.themePath?{themePath:args.themePath}:{},...theme.colorsDark?{darkThemeOptions:adapter.themeToNative(theme,!0)}:{},sourceTheme:theme,apply:args.apply??!1});return jsonResult({files:result.files})}let tailwindMajor=args.tailwind??detectTailwindMajor(out),result=await emitTheme(theme,{outputDir:out,...args.cssPath?{cssPath:args.cssPath}:{},apply:args.apply??!1,cssOnly:args.cssOnly,customCss:ctx.folder.customCss,...tailwindMajor===3?{tailwindMajor:3}:{}});return jsonResult({files:result.files,...result.warnings.length>0?{warnings:result.warnings}:{},...result.notes.length>0?{notes:result.notes}:{}})})}var PropControlSchema=_enum(["boolean","number","string","color","enum","icon"]),ExtensionPropArg=object({name:string().min(1),type:string().min(1),optional:boolean(),control:PropControlSchema,defaultValue:string().optional(),enumValues:array(union([string(),number()])).optional()});function registerExtensionTools(mcp,ctx){mcp.registerTool("add_extension",{description:'Register one of the app\'s own components \u2014 a bespoke DataTable, a brand Hero, a custom Chart \u2014 so screens can use it. The canvas draws a placeholder and `emit_code` writes a real import from `importPath`; `render: "live"` bundles and mounts the actual component instead (charts above all). For compositions of components that already exist, use `add_snippet`. Guide: velloo://guide/extensions.',inputSchema:{id:string().min(1),importPath:string().min(1),props:array(ExtensionPropArg),category:_enum(["ui","typography"]).optional(),description:string().optional(),render:_enum(["static","live"]).optional().describe('"live" client-mounts the real component (charts); default "static" placeholder'),app:string().min(1).optional().describe(`monorepos only: which host app the component lives in \u2014 a config.hostApps key (e.g. "web", "admin"). The live island bundles from that app's root + node_modules. Omit for the default host app.`),fit:_enum(["aspect-video","content"]).optional().describe('live-island sizing: "aspect-video" (default) locks a 16:9 box; "content" lets a fixed-height chart (e.g. ResponsiveContainer height={300}) or an absolute-inset overlay drive its own height')}},async(args)=>toMcp(await addExtension(ctx,args))),mcp.registerTool("update_extension",{description:"Patch an existing extension \u2014 add or remove props, fix `importPath` after a refactor, update the description. Sparse: unlisted fields keep their values. **To rename**, remove and re-add instead; renaming would break every existing tree reference.",inputSchema:{id:string().min(1),patch:object({importPath:string().min(1).optional(),props:array(ExtensionPropArg).optional(),category:_enum(["ui","typography"]).optional(),description:string().optional(),render:_enum(["static","live"]).optional(),app:string().min(1).optional(),fit:_enum(["aspect-video","content"]).optional()})}},async(args)=>toMcp(await updateExtension(ctx,args))),mcp.registerTool("remove_extension",{description:"Unregister an extension. Refuses while any screen or snippet tree still references it, returning the offending nodes so you can remove them or swap their `$ref` first, then call again.",inputSchema:{id:string().min(1)}},async(args)=>toMcp(await removeExtension(ctx,args)))}var MAX_SENDS_PER_SESSION=20;function registerFeedbackTool(mcp,ctx,cloud){let sent=0;mcp.registerTool("send_feedback",{description:"Send free-text feedback about **Velloo itself** \u2014 a confusing instruction, a missing capability, a tool that misbehaved. NOT for feedback about the user's design. Show the user the exact `body` and get their go-ahead first; never send unprompted, and never include their design content, code, or file paths. Sent anonymously unless they opted into contact \u2014 a blind-signed token replaces the account credential \u2014 but needs a signed-in session either way. On failure the error's `reason` says whether to ask the user to sign in, retry later, or just carry on.",inputSchema:{body:string().min(1).max(6000).describe("The feedback in plain prose. No design content, code, or file/repo paths.")}},async({body})=>{if(sent>=MAX_SENDS_PER_SESSION)return errorResult(feedbackError("session-limit",`Feedback limit reached for this session (${MAX_SENDS_PER_SESSION}). Don't retry; carry on with the task.`,!1));let token=await currentToken(cloud);if(!token)return errorResult(feedbackError("signed-out","Not signed in to velloo-cloud, so feedback can't be sent. Tell the user they can run `velloo login` if they want to send it, then carry on \u2014 don't retry without that.",!1));if(!ctx.folder.config.feedback?.contactOk){let result=await sendAnonymousFeedback(cloud,{body,toolVersion:ctx.folder.config.toolVersion,source:"agent"});if(!result.ok)return errorResult(result.error);return sent+=1,jsonResult({message:result.value})}try{let res=await fetch(`${cloud.url}/v1/feedback`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${token}`},body:JSON.stringify({body,source:"agent",toolVersion:ctx.folder.config.toolVersion,contactOk:ctx.folder.config.feedback?.contactOk??!1}),signal:AbortSignal.timeout(5000)});if(!res.ok)return errorResult(feedbackError("rejected",`velloo-cloud refused the message (${res.status}).`,!1));return sent+=1,jsonResult({message:"Thanks \u2014 your feedback was sent."})}catch{return errorResult(feedbackError("unreachable","Couldn't reach velloo-cloud; feedback not sent. Carry on with the task.",!0))}})}function registerGenerateTools(mcp,ctx,cloud){mcp.registerTool("generate_asset",{description:"Generate image assets from a prompt into assets/. **PAY-AS-YOU-GO against the user's credit balance** \u2014 author it yourself with `upload_asset` when you can, check `list_assets` first, iterate at `count: 1`, and ALWAYS relay the returned cost and remaining balance to the user. `intent` picks the model; `reference` restyles or edits an existing asset. Guide: velloo://guide/art.",inputSchema:{prompt:string().min(1).max(2000).describe("What to generate, in plain language. Describe subject, style, lighting and mood \u2014 these models reward detail."),intent:_enum(INTENTS).describe("What the artwork is for; picks the model. See above."),aspect:_enum(ASPECTS).optional().describe('Shape of the output. Defaults per intent ("16:9" for photo/graphic/texture, "1:1" for illustration/icon/vector). Ignored by edit/cutout/upscale, which follow the source.'),count:number().int().min(1).max(4).optional().describe("Variants to generate in one call (default 1, max 4). EACH ONE IS CHARGED. Use 2-4 to give the user a choice on a hero asset; use 1 while iterating on a prompt."),reference:array(string()).max(3).optional().describe('Existing assets to work from, as folder paths ("assets/hero.png"). REQUIRED by edit/cutout/upscale. Optional for photo/illustration/graphic, where it guides style or subject \u2014 e.g. "a stylized version of this landscape".'),filename:string().optional().describe("Stem for assets/<filename>.<png|svg>; default: the generation id. With count > 1 the variants become <filename>-1, <filename>-2, \u2026")}},async({prompt,intent,aspect,count,reference,filename})=>{let r=await generateAsset(ctx.folder.root,cloud,{prompt,intent,aspect,count,reference,filename});if(!r.ok)return errorResult(`generate_asset: ${describeGenerateFailure(r.error)}`);return jsonResult(r.value)})}import{readFileSync as readFileSync9}from"fs";import{join as join33}from"path";function measure(node){return{path:node.nodePath??"",tag:node.tag,...node.class?{class:node.class}:{},rect:node.rect,style:node.style}}function measuredAt(dom,path){let key=path.join("."),found=dom.nodes.find((n)=>n.nodePath===key);return found?measure(found):null}function childrenMeasuredAt(dom,path,limit=12){let prefix=path.length===0?"":`${path.join(".")}.`,out=[];for(let node of dom.nodes){let p=node.nodePath;if(p===void 0||!p.startsWith(prefix)||p===prefix.slice(0,-1))continue;if(p.slice(prefix.length).includes("."))continue;if(out.push(measure(node)),out.length>=limit)break}return out}function overlap(a,b){let ix=Math.max(0,Math.min(a.x+a.w,b.x+b.w)-Math.max(a.x,b.x)),iy=Math.max(0,Math.min(a.y+a.h,b.y+b.h)-Math.max(a.y,b.y)),area=a.w*a.h;return area>0?ix*iy/area:0}function counterpartOf(reference,rect){let best=null,bestScore=0;for(let node of reference.nodes){let score=Math.min(overlap(rect,node.rect),overlap(node.rect,rect));if(score>bestScore)bestScore=score,best=node}return best&&bestScore>=0.6?measure(best):null}function styleDifferences(design,page,limit=8){let out=[];for(let property of new Set([...Object.keys(design.style),...Object.keys(page.style)])){let a=design.style[property]??"",b=page.style[property]??"";if(a!==b)out.push({property,design:a,page:b});if(out.length>=limit)break}return out}function readCaptureDom(folderRoot,captureId,folderId){try{return JSON.parse(readFileSync9(join33(captureDir(folderRoot,captureId,folderId),"dom.json"),"utf8"))}catch{return null}}function styleDiffForRegions(regions,design,reference,scaleFactor,limit=5){let out=[],seen=new Set;for(let region of regions){if(out.length>=limit)break;if(!region.node)continue;let key=region.node.path.join(",");if(seen.has(key))continue;let designNode=measuredAt(design,region.node.path);if(!designNode)continue;let counterpart=counterpartOf(reference,{x:region.x/scaleFactor,y:region.y/scaleFactor,w:region.w/scaleFactor,h:region.h/scaleFactor});if(!counterpart)continue;seen.add(key);let differs=styleDifferences(designNode,counterpart),sizeDiffers=designNode.rect.w!==counterpart.rect.w||designNode.rect.h!==counterpart.rect.h;if(differs.length===0&&!sizeDiffers)continue;out.push({node:`${region.node.ref??"node"}${region.node.id?`#${region.node.id}`:""} @[${key}]`,path:region.node.path,...sizeDiffers?{size:{design:`${designNode.rect.w}\xD7${designNode.rect.h}`,page:`${counterpart.rect.w}\xD7${counterpart.rect.h}`}}:{},differs})}return out}function registerInspectTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler);mcp.registerTool("inspect",{description:"Return the node's server-rendered HTML, resolved className list, $ref, and resolved props for the node at path. Use this instead of guessing the rendered output. The HTML comes from Velloo's bundled library; where the screen mounts the app's own components (component_status { screen }), the canvas and captures show those instead, so measure them with `computed: true`. When path resolves to a snippet instance, pass innerPath to inspect a node *inside* the resolved body (args, $overrides, and $extraClassName applied) \u2014 omit it to inspect the body root. Pass `computed: true` to also render the screen in a real browser and report the node's actual geometry and resolved styles \u2014 use it instead of predicting which utility class won the cascade.",inputSchema:{screenId:string(),path:LocatorSchema,innerPath:string().optional().describe('For a snippet instance: a dotted index path like "0.2", an "@id" of a node in the body, or "" for the body root (the default). Ignored for plain components.'),computed:boolean().optional().describe("Measure the node in a real browser: box geometry and resolved computed styles, plus its direct children's boxes. Costs a render, so it's off by default."),viewport:ViewportArgSchema.optional().describe("Render size for `computed`; defaults to the folder's Desktop preset"),mode:RenderModeSchema.describe("Render variant for `computed`"),theme:ThemeNameSchema.describe("Named theme for `computed`")}},async(args)=>{let result=await inspect2(ctx,args);if(!result.ok)return errorResult(result.error);if(args.computed!==!0)return jsonResult(result.value);let screen=ctx.folder.screens.get(args.screenId);if(!screen)return errorResult(`Screen not found: ${args.screenId}`);if(args.innerPath!==void 0&&args.innerPath!=="")return jsonResult({...result.value,computed:null,computedNote:"A node inside a snippet body isn't separately addressable in the render \u2014 it carries the instance's path. Measure the instance, or render the snippet with render_snippet."});let located=resolve9(screen.tree,args.path,args.screenId);if(!located.ok)return errorResult(located.error);let path=located.value,themeName=args.theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,args.screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}let themeRes=resolveNamedTheme(ctx.folder,themeName);if(!themeRes.ok)return errorResult(themeRes.message);let defaults=defaultViewport(ctx.folder),viewport={w:args.viewport?.w??defaults.w,h:args.viewport?.h??defaults.h};try{let html=await renderForCapture(ctx,screen,{theme:themeRes.theme,dark:args.mode==="dark",viewport,snapshotCss:await jit.build(),liveUrl,canvasBundle,assetOrigin}),dom=await measureRendered({html,viewport}),node=measuredAt(dom,path),diagnostics=await mountDiagnostics(ctx,canvasBundler,screen);return jsonResult({...result.value,...diagnostics.length>0?{diagnostics}:{},computed:node?{viewport,...node,children:childrenMeasuredAt(dom,path)}:null,...node?{}:{computedNote:"The node rendered nothing measurable \u2014 it is display:none, zero-area, or its component drops the props it was given. A zero-area box is itself the finding."}})}catch(err){let timeout=captureTimeoutMessage(err,"inspect");return errorResult(timeout??`inspect: computed measurement failed: ${String(err)}`)}}),mcp.registerTool("find_nodes",{description:'Query a screen tree for nodes matching filters (ANDed): exact $ref, exact $snippet, exact $id, className substring, or prop presence/value. Use this to locate targets for update_props/move_node instead of fetching and walking the whole tree. Example: { screenId: "home", ref: "Icon", prop: "name", propValue: "Github" }.',outputSchema:FindNodesOutput,inputSchema:{screenId:string(),ref:string().optional().describe("Exact component $ref, e.g. 'Button'"),snippetId:string().optional().describe("Exact $snippet id for snippet instances"),id:string().optional().describe("Exact $id anchor"),classContains:string().optional().describe("Substring of props.className"),prop:string().optional().describe("Prop key that must be present"),propValue:unknown().optional().describe("With prop: strict-equal value match"),limit:number().int().positive().optional().describe("Max matches; default 50")}},async(args)=>{let result=await findNodes(ctx,args);return result.ok?structuredResult({...result.value}):errorResult(result.error)})}async function toMcpWithWarnings(result,warn,diagnose){if(!result.ok)return errorResult(result.error);let[propWarnings,diagnostics]=await Promise.all([warn(result.value).catch(()=>[]),diagnose?.(result.value).catch(()=>[])??[]]);return jsonResult(propWarnings.length>0||diagnostics.length>0?{...result.value,...propWarnings.length>0?{propWarnings}:{},...diagnostics.length>0?{diagnostics}:{}}:result.value)}function registerMutationTools(mcp,ctx,jit){mcp.registerTool("update_props",{description:"Patch nodes on a screen: one entry per node in `patches`, applied in one atomic write \u2014 length 1 for a single edit. `propPatch` shallow-merges props (null removes a key). `style` restyles through the screen's *native* channel, which the framework adapter routes to a Tailwind `className` string (shadcn), an `sx` object (MUI), or a plain `style` object; object channels merge shallowly and `style: null` clears. An entry may carry either or both. This patches plain screen nodes \u2014 for one inside a snippet, see velloo://guide/snippets.",inputSchema:updatePropsShape},async(args)=>toMcpWithWarnings(await updateProps2(ctx,args),async()=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return[];let all=[];for(let patch of args.patches){let resolved=resolve9(screen.tree,patch.path,args.screenId);if(!resolved.ok)continue;let node=pathAt(screen.tree,resolved.value);if(node&&isComponentNode(node))all.push(...await propWarnings(ctx,screen,node.$ref,patch.propPatch??{}))}return all},async()=>{let screen=ctx.folder.screens.get(args.screenId);if(!screen)return[];let all=renderDiagnostics(ctx,screen);for(let patch of args.patches){let resolved=resolve9(screen.tree,patch.path,args.screenId);if(!resolved.ok)continue;let node=pathAt(screen.tree,resolved.value);if(node)all.push(...await diagnosticsForTree(ctx,jit,screen,node,resolved.value))}return all})),mcp.registerTool("remove_node",{description:'Remove the node at path. A root locator ([] or "@root") clears all root children; use `compose` mode "replace" when rebuilding the complete screen.',inputSchema:removeNodeShape},async(args)=>toMcp(await removeNode2(ctx,args))),mcp.registerTool("move_node",{description:"Move a node from fromPath to a new parent. toIndex is the insertion index in the destination's children.",inputSchema:moveNodeShape},async(args)=>toMcp(await moveNode2(ctx,args))),mcp.registerTool("set_node_id",{description:'Set or clear the `$id` anchor on a node. Once set, the node is addressable as "@<id>" in any path-accepting tool. Pass `id: null` to clear. Per-screen uniqueness is enforced.',inputSchema:setNodeIdShape},async(args)=>toMcp(await setNodeId2(ctx,args))),mcp.registerTool("add_screen",{description:'Create a NEW screen. It is not placed on any board \u2014 call `add_frame` to surface it. Pass `fromScreenId` to clone an existing tree, or `tree` to supply one; omit `id` to auto-suffix a unique one. A route scan already scaffolds one placeholder screen per detected route (id = route slug), so don\'t add_screen for those \u2014 rebuild the existing screen with `compose` mode "replace".',inputSchema:addScreenShape},async(args)=>toMcpWithWarnings(await addScreen2(ctx,args),async()=>{let created=args.id??"",screen=ctx.folder.screens.get(created)??[...ctx.folder.screens.values()].find((s)=>s.name===args.name);return screen?propWarningsForTree(ctx,screen,screen.tree):[]},async()=>{let created=args.id??"",screen=ctx.folder.screens.get(created)??[...ctx.folder.screens.values()].find((s)=>s.name===args.name);return screen?diagnosticsForScreen(ctx,jit,screen):[]})),mcp.registerTool("update_screen",{description:"Update screen metadata. Sparse patch \u2014 only `name` is patchable today. Screen id stays stable.",inputSchema:updateScreenShape},async(args)=>toMcp(await updateScreen2(ctx,args))),mcp.registerTool("remove_screen",{description:"Delete a screen. Refuses if it's the last screen. If any frames reference the screen, they're returned in `removedFrameIds` (cascaded removal).",inputSchema:removeScreenShape},async(args)=>toMcp(await removeScreen2(ctx,args))),mcp.registerTool("add_board",{description:"Create a new empty board \u2014 one infinite canvas of frames, and a folder can have many. `group` files it under a sidebar group: an existing group's name or id, or a new name to create that group. Guide: velloo://guide/boards.",inputSchema:addBoardShape},async(args)=>toMcp(await addBoard2(ctx,args))),mcp.registerTool("update_board",{description:"Update a board's metadata. `theme` names a theme its frames render with \u2014 the per-board look; null clears back to the folder default. `archived: true` files the board away (hidden from the sidebar, list_boards and a default publish, but kept and still editable). `group` moves it to a sidebar group by existing name or id, a new name (which creates one), or null for Ungrouped. Guide: velloo://guide/boards.",inputSchema:updateBoardShape},async(args)=>toMcp(await updateBoard2(ctx,args))),mcp.registerTool("remove_board",{description:"Delete a board and its notes. Screens no other board places are deleted with it and returned in `removedScreenIds`; screens also framed elsewhere are kept. Snippets those screens were the last to reach go too, in `removedSnippetIds` \u2014 a snippet already unreferenced before the call is left alone. Permanent \u2014 to file a board away reversibly, prefer update_board { patch: { archived: true } }.",inputSchema:removeBoardShape},async(args)=>toMcp(await removeBoard2(ctx,args))),mcp.registerTool("reorder_boards",{description:"Set the left-sidebar display order of boards. `order` lists board ids in the desired order; unknown ids are ignored and omitted boards keep their current slots.",inputSchema:reorderBoardsShape},async(args)=>toMcp(await reorderBoards2(ctx,args))),mcp.registerTool("update_viewport_presets",{description:"Replace the folder's viewport presets \u2014 the sizes offered when adding a frame. Send the complete list in display order; names must be distinct and at least one is required. Frames store their own w/h, so this never resizes an existing frame.",inputSchema:updateViewportPresetsShape},async(args)=>toMcp(await updateViewportPresets2(ctx,args))),mcp.registerTool("add_frame",{description:"Place a screen on a specific board at a chosen size + position. x/y default to a free spot on that board.",inputSchema:addFrameShape},async(args)=>toMcp(await addFrame2(ctx,args))),mcp.registerTool("update_frame",{description:"Move, resize, relabel or regroup frames on a board: one entry per frame in `patches`, applied in one atomic write \u2014 length 1 for a single frame. `label`, `group` and `scheme` accept null to clear; an omitted field is unchanged. `scheme` pins a frame's render mode \u2014 a review affordance over the screen's one shared tree, not a separate design variant.",inputSchema:updateFrameShape},async(args)=>toMcp(await updateFrames2(ctx,args))),mcp.registerTool("remove_frame",{description:"Remove a frame placement from a board. The underlying screen is left intact.",inputSchema:removeFrameShape},async(args)=>toMcp(await removeFrame2(ctx,args))),mcp.registerTool("add_snippet",{description:'Create a reusable subtree with typed `params`; placeholders in the body are `{ "$param": "name" }`. **Structure that varies between instances is still ONE snippet: declare a `node` param** rather than inlining the repetition \u2014 the most expensive mistake here. Call `render_snippet` afterwards; $param wiring bugs are silent until instantiation. Guide: velloo://guide/snippets.',inputSchema:addSnippetShape},async(args)=>toMcpWithWarnings(await addSnippet2(ctx,args),async(value)=>dynamicIconWarningsForTree(value.snippet.tree),async(value)=>diagnosticsForTree(ctx,jit,value.snippet,value.snippet.tree))),mcp.registerTool("update_snippet",{description:"Update a snippet's metadata or body; sparse patch, and every screen using it is re-broadcast. `innerPatch` retargets one node inside the body across every instance without resending the tree; pass `tree` only for a full body replacement. Guide: velloo://guide/snippets.",inputSchema:updateSnippetShape},async(args)=>toMcpWithWarnings(await updateSnippet2(ctx,args),async(value)=>{let warnings=[...await dynamicIconWarningsForTree(value.snippet.tree)];if(args.patch.params!==void 0){let declared=new Set(value.snippet.params.map((p)=>p.name));for(let inst of findSnippetInstances(ctx.folder,value.snippet.id)){let{missing}=resolveSnippetArgs(value.snippet,inst.args),extras=Object.keys(inst.args).filter((k)=>!declared.has(k));if(missing.length===0&&extras.length===0)continue;let parts=[missing.length?`missing required: ${missing.join(", ")}`:"",extras.length?`unknown: ${extras.join(", ")}`:""].filter(Boolean).join("; "),at=inst.path===""?"the root":`path [${inst.path.replace(/\./g,",")}]`;warnings.push(`param change strands the instance on "${inst.screenId}" at ${at} (${parts}) \u2014 fix its args with update_snippet_instance before rendering that screen`)}}return warnings},async(value)=>diagnosticsForTree(ctx,jit,value.snippet,value.snippet.tree))),mcp.registerTool("remove_snippet",{description:"Delete a snippet. Refuses with SnippetInUse while anything still instantiates it; the payload splits the referencers into `screenIds` (clear with remove_node) and `snippetIds` \u2014 other snippets whose body embeds this one (clear with update_snippet).",inputSchema:removeSnippetShape},async(args)=>toMcp(await removeSnippet2(ctx,args))),mcp.registerTool("update_snippet_instance",{description:"Edit ONE snippet instance without touching the shared definition. `argPatch` changes what the caller passes in; `extraClassName` replaces its per-instance class suffix; `innerPath` + `propPatch` patch a node inside just this instance's body \u2014 the \"this card's badge is red\" escape hatch. They compose in one call. To change every instance instead, use `update_snippet`'s innerPatch. Guide: velloo://guide/snippets.",inputSchema:updateSnippetInstanceShape},async(args)=>{let plan=normalizeUpdateSnippetInstance(args);if(!plan.ok)return errorResult(badRequest(plan.message,plan.issues));return toMcpWithWarnings(await updateSnippetInstance(ctx,plan.args),async()=>[],async()=>{let screen=ctx.folder.screens.get(args.screenId);return screen?diagnosticsForScreen(ctx,jit,screen):[]})})}function registerNoteTools(mcp,ctx){mcp.registerTool("add_note",{description:"Add a sticky note to a board. Either free-floating at board coordinates (same space as frame x/y; place beside frames, not on them) or attached to a node \u2014 pass `attachment` and the canvas anchors the note to that node with a connector, auto-placing it beside the frame. body is markdown-lite.",inputSchema:{boardId:string(),x:number().optional().describe("Board x; required unless `attachment` is given"),y:number().optional().describe("Board y; required unless `attachment` is given"),width:number().positive().optional().describe("Pixel width; default 240"),body:string(),attachment:object({frameId:string().describe("Frame on this board that shows the screen"),screenId:string(),locator:LocatorSchema}).optional().describe("Anchor the note to a node instead of free board space")}},async(args)=>toMcp(await addNote2(ctx,args))),mcp.registerTool("update_note",{description:"Patch a board note's position, width, or body.",inputSchema:{boardId:string(),noteId:string(),patch:object({x:number().optional(),y:number().optional(),width:number().positive().optional(),body:string().optional()})}},async(args)=>toMcp(await updateNote2(ctx,args))),mcp.registerTool("remove_note",{description:"Remove a board note by id.",inputSchema:{boardId:string(),noteId:string()}},async(args)=>toMcp(await removeNote2(ctx,args))),mcp.registerTool("add_annotation",{description:'Pin a markdown annotation to a specific node (questions for the designer, review remarks). The canvas draws a connector to the node in every frame showing it. Created with author: "agent" \u2014 you may remove your own annotations later, but user-authored ones are read-only to you.',inputSchema:{screenId:string(),path:LocatorSchema,body:string(),collapsed:boolean().optional()}},async({screenId,path,body,collapsed})=>toMcp(await addAnnotation2(ctx,{screenId,target:{locator:path},body,collapsed,author:"agent"}))),mcp.registerTool("update_annotation",{description:"Edit one of your own annotations \u2014 its body, or `collapsed` (null clears the override). Refuses user-authored ones: those are the designer's channel to you, so answer them with your own annotation rather than rewriting theirs.",inputSchema:{screenId:string(),annotationId:string(),body:string().optional(),collapsed:boolean().nullable().optional()}},async({screenId,annotationId,body,collapsed})=>{if(body===void 0&&collapsed===void 0)return errorResult(badRequest("update_annotation: pass body, collapsed, or both."));let existing=ctx.folder.annotations.get(screenId)?.find((a)=>a.id===annotationId);if(existing&&existing.author!=="agent")return errorResult(badRequest(`Annotation ${annotationId} is user-authored \u2014 agents may only edit their own.`));return toMcp(await updateAnnotation2(ctx,{screenId,annotationId,patch:{...body!==void 0?{body}:{},...collapsed!==void 0?{collapsed}:{}}}))}),mcp.registerTool("remove_annotation",{description:"Remove an agent-authored annotation. Refuses user-authored ones \u2014 those are the designer's channel to you; act on them, don't delete them.",inputSchema:{screenId:string(),annotationId:string()}},async({screenId,annotationId})=>{let existing=ctx.folder.annotations.get(screenId)?.find((a)=>a.id===annotationId);if(existing&&existing.author!=="agent")return errorResult(badRequest(`Annotation ${annotationId} is user-authored \u2014 agents may only remove their own.`));return toMcp(await removeAnnotation2(ctx,{screenId,annotationId}))})}import{readFileSync as readFileSync10}from"fs";import{isAbsolute as isAbsolute5,join as join34}from"path";function urlCacheKey(p){return JSON.stringify({url:p.url,w:p.w,h:p.h,fullPage:p.fullPage,scale:p.scale,dark:p.dark,storageStatePath:p.storageStatePath,cookies:p.cookies??null,localStorage:p.localStorage??null})}function planUrlCache(opts){let{cacheUrl,refreshUrl,prior,now,ttlMs}=opts,active=cacheUrl||refreshUrl,reuse=cacheUrl&&!refreshUrl&&prior&&now-prior.capturedAt<=ttlMs?prior:null;return{active,reuse,storeEligible:active&&reuse===null}}class LruMap{cap;entries=new Map;bytes=0;maxBytes;sizeOf;constructor(cap,opts={}){this.cap=cap;this.maxBytes=opts.maxBytes,this.sizeOf=opts.sizeOf??(()=>0)}get(key){return this.entries.get(key)}set(key,value){let prev=this.entries.get(key);if(prev!==void 0)this.bytes-=this.sizeOf(prev);this.entries.delete(key),this.entries.set(key,value),this.bytes+=this.sizeOf(value);while(this.entries.size>this.cap||this.maxBytes!==void 0&&this.bytes>this.maxBytes&&this.entries.size>1){let oldest=this.entries.keys().next().value;if(oldest===void 0)break;let v=this.entries.get(oldest);if(v!==void 0)this.bytes-=this.sizeOf(v);this.entries.delete(oldest)}}get size(){return this.entries.size}}var URL_CACHE_CAP=20,URL_CACHE_MAX_BYTES=67108864,DEFAULT_URL_CACHE_TTL_MS=300000;function storedCaptureRaster(png,manifest,fullPage){let screenshotMode=manifest.geometry?.screenshot?.mode;if(fullPage&&screenshotMode==="viewport")return{ok:!1,message:`capture "${manifest.id}" contains only its viewport. Pass fullPage: false or make a full-page capture.`};if(!fullPage&&screenshotMode!=="viewport"){let size=pngSize(png),viewportWidth=manifest.geometry?.viewportCss.width??manifest.viewport.w,viewportHeight=manifest.geometry?.viewportCss.height??manifest.viewport.h,scale=viewportWidth>0?size.width/viewportWidth:1,scroll=manifest.geometry?.scrollCss??{x:0,y:0};return{ok:!0,png:cropPng(png,{x:Math.round(scroll.x*scale),y:Math.round(scroll.y*scale),w:Math.round(viewportWidth*scale),h:Math.round(viewportHeight*scale)},0)}}return{ok:!0,png}}function storedCaptureDom(dom,geometry,fullPage){if(fullPage||!geometry)return dom;let{x,y}=geometry.scrollCss;return{...dom,viewport:{w:geometry.viewportCss.width,h:geometry.viewportCss.height},documentHeight:geometry.viewportCss.height,nodes:dom.nodes.map((node)=>({...node,rect:{...node.rect,x:node.rect.x-x,y:node.rect.y-y}}))}}var UrlCookieSchema=object({name:string(),value:string(),url:string().optional(),domain:string().optional(),path:string().optional()}),LiveUrlSource=strictObject({url:string().describe("e.g. http://localhost:3000/pricing"),auth:strictObject({storageStatePath:string().optional().describe("Playwright storage-state JSON (logged-in cookies + localStorage) \u2014 absolute, or relative to the design folder. The robust way past an auth gate."),cookies:array(UrlCookieSchema).optional().describe("Session cookies seeded before navigating (each needs url OR domain+path)"),localStorage:record(string(),string()).optional().describe("localStorage entries seeded before any page script runs (e.g. a JWT)")}).optional().describe("Credentials for a page behind a login"),settleTimeoutMs:number().int().positive().optional().describe("Max ms to wait for network quiet before capturing (default 8000). Raise it for a data-heavy page that paints a spinner first, so the shot fires once async data has settled rather than capturing the spinner (which reads as unverified)."),cache:strictObject({freeze:boolean().optional().describe("Capture the page once and diff every later call against that frozen reference. Essential for a DYNAMIC page (feed, dashboard, per-user content): without it each call re-fetches, the content drifts, and `similarity` jitters so you cannot tell a design change from a page change."),ttlMs:number().int().positive().optional().describe("How long a frozen capture stays valid; default 300000 (5 min)"),refresh:boolean().optional().describe("Re-capture now and replace the frozen reference \u2014 after fixing the target app. Implies freeze.")}).optional()}),StoredCaptureSource=strictObject({captureId:string().describe("From list_captures")});function similarityNote(input){let{similarity,contentSimilarity,heightDelta}=input,heightDiffers=heightDelta!==0;if(heightDiffers&&contentSimilarity-similarity>=0.05)return`similarity is held down mostly by a ${Math.abs(heightDelta)}px height difference, not by content mismatch \u2014 `+`over the overlapping height the match is ${contentSimilarity}. Small cumulative vertical drift cascades down a long single column and tanks the whole-page pixel diff; trust contentSimilarity and the per-region node refs here.`;if(similarity>=0.3)return null;return`similarity ${similarity} means these two renders differ structurally, not in detail. At this range the diff cannot localize: `+"topMismatches will span most of the page with a null node ref, which restates the score rather than naming a cause \u2014 do not work through them yet. "+(heightDiffers?`The render is ${Math.abs(heightDelta)}px ${heightDelta>0?"taller":"shorter"} than the page (${contentSimilarity} over the overlap), which is itself the lead: at this size the gap is whole sections missing, `+"stacked where the page puts them side by side, or a container a different width \u2014 not spacing. ":"")+"Re-read the capture's outline and rects (get_capture) and fix the page-level structure first; topMismatches start earning their keep past ~0.4."}function registerCompareToUrlTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler),urlCaptures=new LruMap(URL_CACHE_CAP,{maxBytes:URL_CACHE_MAX_BYTES,sizeOf:(c)=>c.result.png.byteLength});mcp.registerTool("compare_to_url",{description:"Code-to-design fidelity check: render a screen and capture the same page from a live URL (or a stored `captureId`) at the same viewport, then pixel-diff. 0.85+ is a faithful structural port; fix `topMismatches` in order and don't chase 1.0. `styleDiff` names the resolved computed properties behind each of those regions (design vs page) \u2014 read it instead of inferring from class strings which utility won. **If the result is `unverified` the similarity is meaningless \u2014 stop and fix the capture rather than iterating against a page you never saw.** Guide: velloo://guide/porting.",inputSchema:{screenId:string(),source:union([LiveUrlSource,StoredCaptureSource]).describe("What to diff against: a live page, or a capture already on disk"),viewport:ViewportArgSchema.optional().describe("Render size; defaults to the folder's Desktop preset"),mode:_enum(["light","dark"]).optional(),fullPage:boolean().optional(),scale:number().min(0.25).max(1).optional().describe("Default 0.5"),theme:ThemeNameSchema.describe("Named theme for the Velloo side; default = the hosting board's pin, else the folder default"),image:boolean().optional().describe("Include the side-by-side PNG (default true; false = metrics only)")},outputSchema:CompareToUrlOutput},async({screenId,source,viewport:vp,mode,fullPage,scale,theme,image})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(`Screen not found: ${screenId}`);let live="url"in source?source:null,url=live?.url,captureId=live?void 0:source.captureId,{storageStatePath,cookies,localStorage}=live?.auth??{},settleTimeoutMs=live?.settleTimeoutMs,cache=live?.cache,themeName=theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}let storedPng=null,storedFinalUrl="",storedViewport=null,storedGeometry,storedStateId;if(captureId!==void 0){if(!isSafeCaptureId(captureId))return errorResult(`Invalid captureId: ${captureId}`);let folderId=ctx.folder.config.folderId,manifest=readCaptureManifest(ctx.folder.root,captureId,folderId);if(!manifest)return errorResult(`compare_to_url: no capture "${captureId}" for this folder \u2014 call list_captures to see what's stored.`);if(manifest.themeOnly||!manifest.files.includes("page.png"))return errorResult(`compare_to_url: capture "${captureId}" is theme-only, so there's no page render to diff against. Capture the page itself in a capture session.`);if(manifest.stability?.status==="unstable")return errorResult(`compare_to_url: capture "${captureId}" changed while its screenshot and outline were being recorded. Re-capture the page once it has settled; Velloo will not present mixed states as a frozen reference.`);try{storedPng=readFileSync10(join34(captureDir(ctx.folder.root,captureId,folderId),"page.png"))}catch{return errorResult(`compare_to_url: capture "${captureId}" is missing its page.png.`)}if(storedFinalUrl=manifest.finalUrl||manifest.url,storedGeometry=manifest.geometry,storedStateId=manifest.stateId,manifest.viewport.w>0)storedViewport=manifest.viewport;let raster=storedCaptureRaster(storedPng,manifest,fullPage!==!1);if(!raster.ok)return errorResult(`compare_to_url: ${raster.message}`);storedPng=raster.png}else{let scheme;try{scheme=new URL(url).protocol}catch{return errorResult(`Invalid url: ${url}`)}if(scheme!=="http:"&&scheme!=="https:")return errorResult(`compare_to_url: only http(s) URLs are allowed (got ${scheme})`)}let defaults=defaultViewport(ctx.folder),viewport={w:vp?.w??defaults.w,h:vp?.h??defaults.h},scaleFactor=scale??0.5,captureDownscale=1,captureResize=null;if(storedPng&&storedViewport){let requested=scale??0.5;scaleFactor=[1,0.5,0.25].reduce((best,s)=>Math.abs(s-requested)<Math.abs(best-requested)?s:best);let storedSize=pngSize(storedPng),ratio=storedSize.width/storedViewport.w/scaleFactor;if(Number.isInteger(ratio)&&ratio>=1)captureDownscale=ratio;else captureResize={width:Math.round(storedSize.width/ratio),height:Math.round(storedSize.height/ratio)};viewport={w:storedViewport.w,h:storedViewport.h||defaults.h}}try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let html=await renderForCapture(ctx,screen,{theme:_themeRes.theme,dark:mode==="dark",viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin}),resolvedStorageState=storageStatePath===void 0?void 0:isAbsolute5(storageStatePath)?storageStatePath:join34(ctx.folder.root,storageStatePath),urlKey=urlCacheKey({url:url??"",w:viewport.w,h:viewport.h,fullPage:fullPage??!0,scale:scaleFactor,dark:mode==="dark",storageStatePath:resolvedStorageState??null,cookies,localStorage}),now=Date.now(),plan=planUrlCache({cacheUrl:storedPng===null&&cache?.freeze===!0,refreshUrl:storedPng===null&&cache?.refresh===!0,prior:urlCaptures.get(urlKey),now,ttlMs:cache?.ttlMs??DEFAULT_URL_CACHE_TTL_MS}),[velloo,urlCapture]=await Promise.all([captureScreenshot({html,viewport,fullPage:fullPage??!0,deviceScaleFactor:scaleFactor,dom:!0}),storedPng?Promise.resolve({png:captureResize?resizePng(storedPng,captureResize.width,captureResize.height):downscalePng(storedPng,captureDownscale),finalUrl:storedFinalUrl,authWall:!1,pageError:null}):plan.reuse?Promise.resolve(plan.reuse.result):captureUrlScreenshot({url,viewport,fullPage:fullPage??!0,deviceScaleFactor:scaleFactor,dark:mode==="dark",dom:!0,...settleTimeoutMs!==void 0?{settleTimeoutMs}:{},...resolvedStorageState?{storageStatePath:resolvedStorageState}:{},...cookies?{cookies}:{},...localStorage?{localStorage}:{}})]),result=diffPngs(urlCapture.png,velloo.png),regions=result.regions.map((r)=>({...r,node:regionNode(r,velloo.nodeRects,scaleFactor,screen)})),totalChanged=Math.max(1,result.changedPixels),topMismatches=regions.slice(0,5).map((r)=>{let share=Math.round((r.changedPixels??0)/totalChanged*100),node=r.node?` \u2192 ${r.node.ref??"node"}${r.node.id?`#${r.node.id}`:""} @[${r.node.path.join(",")}]`:"";return`~${share}% of the diff at (${r.x},${r.y} ${r.w}\xD7${r.h})${node}`}),rawReferenceDom=urlCapture.dom??(captureId!==void 0?readCaptureDom(ctx.folder.root,captureId,ctx.folder.config.folderId):null),referenceDom=rawReferenceDom?storedCaptureDom(rawReferenceDom,storedGeometry,fullPage!==!1):null,styleDiff=velloo.dom&&referenceDom?styleDiffForRegions(regions,velloo.dom,referenceDom,scaleFactor):[],cls=storedPng?{redirected:!1,finalLooksLikeLogin:!1,requestedLooksLikeLogin:!1}:classifyCapture(url,urlCapture.finalUrl),authWall=urlCapture.authWall&&!cls.requestedLooksLikeLogin,unverified=cls.redirected||authWall||urlCapture.pageError!==null;if(plan.storeEligible&&!unverified)urlCaptures.set(urlKey,{result:urlCapture,capturedAt:now});let reason=cls.redirected?`it redirected to ${urlCapture.finalUrl}`:urlCapture.pageError!==null?urlCapture.pageError:"it shows a login form",contentHeight=contentHeightFromRects(velloo.nodeRects),shortFrames=framesShorterThan(ctx,screenId,contentHeight,viewport.w),similarity=Number((1-result.changedRatio).toFixed(4)),contentSimilarity=Number((1-result.contentChangedRatio).toFixed(4)),bitmapHeightDelta=result.heightDelta,heightDelta=Number((bitmapHeightDelta/scaleFactor).toFixed(2)),heightDiffers=heightDelta!==0,note=similarityNote({similarity,contentSimilarity,heightDelta}),diagnostics=[...await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]),...await mountDiagnostics(ctx,canvasBundler,screen)],summary={similarity,changedRatio:Number(result.changedRatio.toFixed(4)),...heightDiffers?{contentSimilarity}:{},heightDelta,bitmapHeightDelta,contentHeight,...shortFrames.length?{framesShorterThanContent:shortFrames}:{},...!unverified&¬e!==null?{note}:{},...storedPng?{capture:{id:captureId,url:storedFinalUrl,...storedGeometry?{geometry:storedGeometry}:{},...storedStateId?{stateId:storedStateId}:{},note:"diffed against a stored browser capture \u2014 already past any login and frozen, so similarity reflects your design changes only."}}:{},...plan.active?{urlCache:plan.reuse?{hit:!0,capturedAt:new Date(plan.reuse.capturedAt).toISOString(),ageMs:now-plan.reuse.capturedAt,note:"diffed against a frozen capture \u2014 similarity reflects design changes only, not page drift. Pass cache.refresh to re-sample."}:{hit:!1,stored:!unverified}}:{},...topMismatches.length?{topMismatches}:{},...styleDiff.length?{styleDiff}:{},regions,...diagnostics.length>0?{diagnostics}:{},...unverified?{unverified:!0,...cls.redirected?{redirected:{requested:url??"",final:urlCapture.finalUrl}}:{},...authWall?{authWall:!0}:{},...urlCapture.pageError!==null?{pageError:urlCapture.pageError}:{},warning:`the capture is NOT a faithful view of your page \u2014 ${reason}. similarity is meaningless here (you're diffing against the wrong thing). For an auth wall, pass storageStatePath/cookies/localStorage; for a broken target, fix the app's dev server first. `+"Either way, don't tune the design to this capture \u2014 leave the screen unverified."}:{}},extra=[];if(image!==!1){let side=sideBySidePng(urlCapture.png,velloo.png);extra.push({type:"image",data:side.toString("base64"),mimeType:"image/png"})}return structuredResult(summary,...extra)}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"compare_to_url");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);if(/net::|ERR_CONNECTION|Timeout.*exceeded|goto/.test(msg))return errorResult(`compare_to_url: could not load ${url??captureId} \u2014 is the app's dev server running? Underlying error: ${msg}`);return errorResult(`compare_to_url failed: ${msg}`)}})}function registerRenderSnippetTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler);mcp.registerTool("render_snippet",{description:"Render a snippet in isolation and return a screenshot plus any class/theme diagnostics. Useful for iterating on a snippet's styling before stamping it. viewport defaults to 480\xD7640.",inputSchema:{snippetId:string(),args:record(string(),unknown()).optional(),extraClassName:string().optional(),viewport:ViewportArgSchema.optional().describe("Defaults to 480\xD7640"),mode:RenderModeSchema,scale:number().min(0.25).max(1).optional(),theme:ThemeNameSchema}},async({snippetId,args,extraClassName,viewport,mode,scale,theme})=>{let snippet=ctx.folder.snippets.get(snippetId);if(!snippet)return errorResult(`Snippet not found: ${snippetId}`);let vp=viewport??{w:480,h:640},screen={id:`${snippet.id}__preview`,name:`${snippet.name} preview`,tree:{$snippet:snippet.id,...args&&Object.keys(args).length>0?{args}:{},...extraClassName&&extraClassName.trim()!==""?{$extraClassName:extraClassName.trim()}:{}}},buf;try{let extraCandidates=[];if(extraClassName)extraCandidates.push(...extraClassName.split(/\s+/));for(let v of Object.values(args??{}))if(typeof v==="string")extraCandidates.push(...v.split(/\s+/));let snapshotCss=await jit.build(extraCandidates.filter(Boolean)),syntheticScreen={...screen,library:snippet.library},_themeRes=resolveNamedTheme(ctx.folder,theme);if(!_themeRes.ok)return errorResult(_themeRes.message);let base={theme:_themeRes.theme,viewport:vp,snapshotCss,liveUrl,canvasBundle,assetOrigin};if(mode==="compare"){let[lightHtml,darkHtml]=await Promise.all([renderForCapture(ctx,syntheticScreen,{...base,dark:!1}),renderForCapture(ctx,syntheticScreen,{...base,dark:!0})]);buf=await screenshotCompareBuffer({leftHtml:lightHtml,rightHtml:darkHtml,viewport:vp,...scale?{deviceScaleFactor:scale}:{}})}else{let html=await renderForCapture(ctx,syntheticScreen,{...base,dark:mode==="dark"});buf=await screenshotBuffer({html,viewport:vp,fullPage:!0,...scale?{deviceScaleFactor:scale}:{}})}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"render_snippet");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`render_snippet failed: ${msg}`)}let diagnostics=await diagnosticsForTree(ctx,jit,snippet,snippet.tree).catch(()=>[]);return{content:[...diagnostics.length>0?[{type:"text",text:JSON.stringify({diagnostics})}]:[],{type:"image",data:buf.toString("base64"),mimeType:"image/png"}]}})}var BASELINE_CAP=20,BASELINE_MAX_BYTES=67108864;function resolveScreenshotMode(folder,screenId,explicit){if(explicit!==void 0)return{ok:!0,mode:explicit};let pinned=pinnedSchemeForScreen(folder,screenId);if(!pinned.ok)return pinned;return{ok:!0,mode:pinned.scheme??"light"}}function registerScreenshotCaptureTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){let liveUrl=makeLiveUrl(ctx,bundler),canvasBundle=makeCanvasBundle(ctx,canvasBundler),baselines=new LruMap(BASELINE_CAP,{maxBytes:BASELINE_MAX_BYTES,sizeOf:(b)=>b.png.byteLength});mcp.registerTool("screenshot",{description:"Render a screen to PNG and run full class/theme diagnostics. `mode: \"compare\"` returns light and dark side by side \u2014 the fastest check that a design adapts; omitted, mode follows the hosting frame's pin. `diff: true` compares against your previous capture. `scale` (0.25\u20131) shrinks the payload; `path` captures one element. The render uses its OWN viewport, not the board frame's, so `framesShorterThanContent` names placements that clip below the fold \u2014 resize them with `update_frame`. Guide: velloo://guide/verification.",inputSchema:{screenId:string(),viewport:ViewportArgSchema.optional().describe("Render size; defaults to the folder's Desktop preset"),mode:RenderModeSchema,fullPage:boolean().optional(),scale:number().min(0.25).max(1).optional(),path:LocatorSchema.optional().describe('Capture only this node \u2014 path array or "@id" (not with mode: "compare"). Omit it (or pass "@root"/[]) to capture the whole screen, which is the default.'),theme:ThemeNameSchema.describe("Named theme to render with; default = the hosting board's pin, else the folder default"),diff:boolean().optional().describe("Compare against the previous same-params capture"),resetBaseline:boolean().optional()}},async({screenId,viewport:vp,mode,fullPage,scale,path,theme,diff,resetBaseline})=>{let screen=ctx.folder.screens.get(screenId);if(!screen)return errorResult(`Screen not found: ${screenId}`);let diagnostics=[...await diagnosticsForScreen(ctx,jit,screen).catch(()=>[]),...await mountDiagnostics(ctx,canvasBundler,screen)],withDiagnostics=(value)=>({...value,...diagnostics.length>0?{diagnostics}:{}}),resolvedMode=resolveScreenshotMode(ctx.folder,screenId,mode);if(!resolvedMode.ok)return errorResult(resolvedMode.message);mode=resolvedMode.mode;let themeName=theme;if(themeName===void 0){let pinned=pinnedThemeForScreen(ctx.folder,screenId);if(!pinned.ok)return errorResult(pinned.message);themeName=pinned.name}if(diff&&(mode==="compare"||path!==void 0))return errorResult('screenshot: diff cannot combine with mode: "compare" or path');let clipSelector;if(path!==void 0){if(mode==="compare")return errorResult('screenshot: path cannot be combined with mode: "compare"');if(!(path==="@root"||Array.isArray(path)&&path.length===0||typeof path==="string"&&path.replace(/\s/g,"")==="[]")){let resolved=resolve9(screen.tree,path,screenId);if(!resolved.ok)return errorResult(JSON.stringify(resolved.error));clipSelector=`[data-node-path="${resolved.value.join(".")}"]`}}let defaults=defaultViewport(ctx.folder),viewport={w:vp?.w??defaults.w,h:vp?.h??defaults.h};if(diff)try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let html=await renderForCapture(ctx,screen,{theme:_themeRes.theme,dark:mode==="dark",viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin}),capture=await captureScreenshot({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{}}),key=JSON.stringify({screenId,w:viewport.w,h:viewport.h,mode,theme:themeName,fullPage:fullPage??!0,scale:scale??1}),baseline=baselines.get(key);if(baselines.set(key,{png:capture.png,rects:capture.nodeRects}),!baseline||resetBaseline)return{content:[{type:"text",text:JSON.stringify(withDiagnostics({diff:{baseline:"established"}}))},{type:"image",data:capture.png.toString("base64"),mimeType:"image/png"}]};let result=diffPngs(baseline.png,capture.png),scaleFactor=scale??1,regions=result.regions.map((r)=>({...r,node:regionNode(r,capture.nodeRects,scaleFactor,screen)})),summary=withDiagnostics({diff:{changedRatio:Number(result.changedRatio.toFixed(4)),changedPixels:result.changedPixels,heightDelta:result.heightDelta,regions,baseline:"updated"}});if(result.changedPixels===0)return{content:[{type:"text",text:JSON.stringify(withDiagnostics({diff:{changedRatio:0,note:"no visual change"}}))}]};if(result.changedRatio<0.4&®ions.length>0){let crop=cropPng(result.diffPng,unionRegion(result.regions));return{content:[{type:"text",text:JSON.stringify(summary)},{type:"image",data:crop.toString("base64"),mimeType:"image/png"}]}}return{content:[{type:"text",text:JSON.stringify({...summary,note:"changes too widespread for an overlay \u2014 new capture follows"})},{type:"image",data:capture.png.toString("base64"),mimeType:"image/png"}]}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"screenshot diff");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`screenshot diff failed: ${msg}`)}let buf,contentText=null;try{let snapshotCss=await jit.build(),_themeRes=resolveNamedTheme(ctx.folder,themeName);if(!_themeRes.ok)return errorResult(_themeRes.message);let base={theme:_themeRes.theme,viewport,snapshotCss,liveUrl,canvasBundle,assetOrigin};if(mode==="compare"){let[lightHtml,darkHtml]=await Promise.all([renderForCapture(ctx,screen,{...base,dark:!1}),renderForCapture(ctx,screen,{...base,dark:!0})]);buf=await screenshotCompareBuffer({leftHtml:lightHtml,rightHtml:darkHtml,viewport,...scale?{deviceScaleFactor:scale}:{}})}else{let html=await renderForCapture(ctx,screen,{...base,dark:mode==="dark"});if(clipSelector)buf=await screenshotBuffer({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{},clipSelector});else{let capture=await captureScreenshot({html,viewport,fullPage:fullPage??!0,...scale?{deviceScaleFactor:scale}:{}});buf=capture.png;let contentHeight=contentHeightFromRects(capture.nodeRects),shortFrames=framesShorterThan(ctx,screenId,contentHeight,viewport.w);contentText=JSON.stringify(withDiagnostics({contentHeight,theme:themeName??"default",viewport:{w:viewport.w,h:viewport.h},...shortFrames.length?{framesShorterThanContent:shortFrames}:{}}))}}}catch(err){let bm=browserErrorMessage(err);if(bm)return errorResult(bm);let tm=captureTimeoutMessage(err,"screenshot");if(tm)return errorResult(tm);let msg=err instanceof Error?err.message:String(err);return errorResult(`screenshot failed: ${msg}`)}let content=[];if(contentText)content.push({type:"text",text:contentText});else if(diagnostics.length>0)content.push({type:"text",text:JSON.stringify({diagnostics})});return content.push({type:"image",data:buf.toString("base64"),mimeType:"image/png"}),{content}})}function registerScreenshotTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin){registerScreenshotCaptureTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerCompareToUrlTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerRenderSnippetTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin)}import{readFile as readFile14}from"fs/promises";import{dirname as dirname15,isAbsolute as isAbsolute6,join as join35}from"path";var CHART_LIBS=["recharts","echarts","echarts-for-react","chart.js","react-chartjs-2","victory","plotly.js","react-plotly.js","@observablehq/plot","@tremor/react","react-vis","d3","@visx","@nivo"];function chartLibsInDeps(deps){let found=new Set;for(let name of Object.keys(deps))for(let lib of CHART_LIBS)if(name===lib||lib.startsWith("@")&&name.startsWith(`${lib}/`))found.add(lib);return[...found].sort()}var TW_CONFIG_NAMES=["tailwind.config.ts","tailwind.config.js","tailwind.config.mjs","tailwind.config.cjs"];async function readTailwindConfig(explicitPath,cssResolvedPath,folderRoot){if(explicitPath){let p=isAbsolute6(explicitPath)?explicitPath:join35(folderRoot,explicitPath);return readFile14(p,"utf8").catch(()=>null)}if(!cssResolvedPath)return null;let dir=dirname15(cssResolvedPath);for(let i=0;i<5;i++){for(let name of TW_CONFIG_NAMES){let hit=await readFile14(join35(dir,name),"utf8").catch(()=>null);if(hit!==null)return hit}let parent=dirname15(dir);if(parent===dir)break;dir=parent}return null}async function readHostPackageJson(folder,cssResolvedPath){let candidates=[];if(folder.config.hostApp?.root)candidates.push(join35(hostAppRootFrom(folder.root,folder.config.hostApp),"package.json"));if(cssResolvedPath){let dir=dirname15(cssResolvedPath);for(let i=0;i<6;i++){candidates.push(join35(dir,"package.json"));let parent=dirname15(dir);if(parent===dir)break;dir=parent}}for(let p of candidates){let txt=await readFile14(p,"utf8").catch(()=>null);if(txt===null)continue;try{return JSON.parse(txt)}catch{}}return null}async function detectChartLibs(folder,cssResolvedPath){let pkg=await readHostPackageJson(folder,cssResolvedPath);if(!pkg)return[];let deps={...pkg.dependencies??{},...pkg.devDependencies??{}};return chartLibsInDeps(deps)}function detectContainer(src){if(src===null)return null;let detected=parseTailwindContainer(src);if(!detected)return null;let suggestedClasses=containerClasses(detected);return{detected,suggestedClasses,note:'applied to the theme \u2014 `class="container"` now centers/pads/caps to match the app. `suggestedClasses` are the equivalent utilities if you\'d rather wrap content in a `Box` explicitly.'}}function registerThemeTools(mcp,ctx){mcp.registerTool("set_theme",{description:"Edit the theme through one verb \u2014 pass any combination of channels. `tokens` patches dot-paths (`colors.*` / `colorsDark.*` are the semantic slots that theme-flip; `palette.*` is a non-flipping passthrough for raw brand colors). `fonts` declares font roles. `typeset` sets the type rhythm the whole ladder derives from \u2014 reach for it instead of per-node `text-*`. `customCss` replaces theme/custom.css. `from` reseeds from a preset or seed color before the other channels apply. Guide: velloo://guide/theme.",inputSchema:{theme:string().optional().describe('Named theme to edit; default "default"'),tokens:record(string(),union([string(),number()])).optional().describe('Dot-path patch, e.g. { "colors.primary.DEFAULT": "#4f46e5" }'),from:union([object({preset:_enum(PRESET_NAMES)}),object({seedColor:string()})]).optional().describe("Reseed the whole palette; applied before the other channels"),fonts:array(object({role:string().describe('Token role, e.g. "display", "sans", "mono" \u2014 any utility-safe name'),family:string().optional().describe('Family name, e.g. "Unbounded". Required unless removing'),fallback:string().optional().describe("CSS stack tail; sensible default per role"),google:union([string(),literal(!0)]).optional().describe('Google Fonts axis spec ("wght@400..900") or true for a plain load'),remove:boolean().optional().describe("Drop this role; refused while a typeset still names it")})).min(1).optional(),typeset:array(object({name:string().optional().describe('Typeset name; default "default" (the folder baseline). Any other name becomes a preset a Prose region opts into'),renameTo:string().optional(),remove:boolean().optional(),size:union([string(),number(),_null()]).optional().describe('Base text size \u2014 "1em" (container-relative), 15, or "15px"'),leading:union([number(),_null()]).optional().describe("Body line-height, unitless. The heading ladder derives from it"),flow:union([string(),number(),_null()]).optional().describe('Space between blocks \u2014 e.g. "1.25em"'),fontBody:union([string(),_null()]).optional().describe("A role from `fonts`"),fontHeading:union([string(),_null()]).optional().describe("A role from `fonts`"),fontMono:union([string(),_null()]).optional().describe("A role from `fonts`")})).min(1).optional(),customCss:string().optional().describe("Replaces theme/custom.css entirely \u2014 read it first via get_theme")}},async(args)=>{let channels=["from","tokens","fonts","typeset","customCss"];if(channels.every((c)=>args[c]===void 0))return errorResult({kind:"BadRequest",message:`set_theme: pass at least one of ${channels.join(", ")}.`});let applied={};if(args.from){let seeded="preset"in args.from?await applyPreset2(ctx,args.from.preset,args.theme):await derivePaletteFromColor(ctx,args.from.seedColor,args.theme);if(!seeded.ok)return errorResult(seeded.error);applied.from=args.from}if(args.tokens){let entries=Object.entries(args.tokens).map(([path,value])=>({path,value})),r=await setTokens2(ctx,entries,args.theme);if(!r.ok)return errorResult(r.error);applied.tokens=r.value.applied;let warnings=[];for(let path of r.value.applied){let name=/^palette(?:Dark)?\.([a-z0-9-]+)$/.exec(path)?.[1];if(name&&SEMANTIC_SLOTS.has(name))warnings.push(`\`${path}\` shadows the semantic slot \`${name}\` and is skipped on emit and in the canvas (the semantic \`--color-${name}\` wins). Set \`colors.${name}\` / \`colorsDark.${name}\` instead, or rename the palette token (e.g. \`brand-${name}\`).`)}if(warnings.length>0)applied.warnings=warnings}if(args.fonts){let r=await setFonts2(ctx,args.fonts,args.theme);if(!r.ok)return errorResult(r.error);applied.fonts=args.fonts.map((f)=>f.role)}if(args.typeset){let r=await setTypeset2(ctx,args.typeset,args.theme);if(!r.ok)return errorResult(r.error);applied.typeset=args.typeset.map((t)=>t.name??"default")}if(args.customCss!==void 0){let r=await setCustomCss2(ctx,args.customCss);if(!r.ok)return errorResult(r.error);applied.customCss={bytes:r.value.bytes}}return jsonResult({theme:args.theme??"default",applied})}),mcp.registerTool("add_theme",{description:"Create a named theme (theme/<name>.json) by cloning an existing one. Boards pin it via update_board { patch: { theme } }; renders accept theme:. Edit it with set_theme's own theme param.",inputSchema:{name:string().describe("Lowercase kebab, not 'default'"),from:string().optional().describe("Source theme to clone; default 'default'"),overwrite:boolean().optional()}},async(args)=>toMcp(await addTheme(ctx,args.name,args.from,args.overwrite??!1))),mcp.registerTool("update_theme",{description:"Rename a named theme, repointing every board that pinned it (the response lists them). To change a theme's *tokens*, use set_theme with its `theme` param.",inputSchema:{name:string().describe("Theme to rename"),renameTo:string().describe("New name \u2014 lowercase kebab, not 'default'")}},async(args)=>toMcp(await renameTheme(ctx,args.name,args.renameTo))),mcp.registerTool("remove_theme",{description:"Delete a named theme. Refuses while any board still pins it, naming those boards \u2014 repoint or unpin them first with update_board { patch: { theme } }. The default theme cannot be removed.",inputSchema:{name:string()}},async(args)=>toMcp(await removeTheme(ctx,args.name))),mcp.registerTool("list_themes",{description:"List named themes and which boards use each.",inputSchema:{}},async()=>jsonResult(listThemes(ctx))),mcp.registerTool("import_theme",{description:"Code-to-design: seed the theme from an existing app's stylesheet instead of picking colors by hand \u2014 semantic slots, the raw `palette.*` passthrough for brand vars, fonts, radius, and the nearby tailwind.config's `theme.extend`. Dry-run by default; pass `apply: true` to persist. Run this BEFORE porting screens. Guide: velloo://guide/theme.",inputSchema:{css:string().optional().describe("Stylesheet text (use this OR cssPath)"),cssPath:string().optional().describe("Path to the stylesheet \u2014 absolute, or relative to the HOST APP root (where globals.css lives, normally OUTSIDE the design folder); the design folder is tried as a last resort"),theme:string().optional().describe('Named theme to merge into; default "default"'),apply:boolean().optional().describe("Persist the merge (default false = dry-run)"),tailwindConfigPath:string().optional().describe("Path to the app's tailwind.config (absolute, or relative to the design folder). Auto-detected near `cssPath` when omitted; its `theme.extend` (colors, boxShadow, fontFamily) is ingested and its `container` reported as guidance.")}},async(args)=>{let css=args.css?.trim()?args.css:void 0,cssResolvedPath;if(css===void 0){if(args.cssPath===void 0)return errorResult({kind:"BadRequest",message:"pass either `css` text or a `cssPath`"});let hostRoot=ctx.folder.config.hostApp?.root,bases=isAbsolute6(args.cssPath)?[""]:[...hostRoot?[hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp)]:[],join35(ctx.folder.root,".."),ctx.folder.root],tried=[];for(let base of bases){let candidate=base===""?args.cssPath:join35(base,args.cssPath);tried.push(candidate);let text=await readFile14(candidate,"utf8").catch(()=>null);if(text!==null){css=text,cssResolvedPath=candidate;break}}if(css===void 0)return errorResult({kind:"BadRequest",message:`could not read "${args.cssPath}" \u2014 tried ${tried.map((t)=>`"${t}"`).join(", ")}. The host app's globals.css usually lives OUTSIDE the design folder (the design folder is at <appRoot>/velloo). Pass an absolute path, a path relative to the host app root, or paste the stylesheet text directly as \`css\`.`})}let tailwindConfig=await readTailwindConfig(args.tailwindConfigPath,cssResolvedPath,ctx.folder.root),r=await importThemeCss2(ctx,css,{...args.theme!==void 0?{themeName:args.theme}:{},...args.apply!==void 0?{apply:args.apply}:{},...tailwindConfig!==null?{tailwindConfig}:{}});if(!r.ok)return errorResult(r.error);let{changes,coverage,warnings,applied}=r.value,container=detectContainer(tailwindConfig),detectedChartLibs=await detectChartLibs(ctx.folder,cssResolvedPath);return jsonResult({applied,changeCount:changes.length,coverage:{...coverage,summary:`${coverage.semantic} of ${coverage.semanticTotal} semantic slots mapped; ${coverage.palette} palette passthrough`},changes,warnings,...container?{container}:{},...detectedChartLibs.length>0?{detectedChartLibs,chartHint:`this app uses a client chart library \u2014 register its chart components as render:"live" extensions (add_extension) so the canvas preview AND emitted code use the app's real charts; the built-in Chart previews via echarts and will not pixel-match.`}:{},...applied?{}:{note:"dry-run \u2014 pass apply: true to persist these changes"}})}),mcp.registerTool("score_theme_contrast",{description:"Score WCAG contrast for a theme's salient color pairs in both palettes \u2014 ratio + tier (AAA / AA / AAlarge / Fail) per pair. Run it after reseeding a palette or tuning dark tokens.",inputSchema:{mode:_enum(["light","dark"]).optional().describe("Score only this palette; default both"),theme:string().optional().describe('Named theme to score; default "default"')}},async(args)=>{let resolved=resolveNamedTheme(ctx.folder,args.theme);if(!resolved.ok)return errorResult({kind:"BadRequest",message:resolved.message});let results=args.mode?scoreThemeContrast(resolved.theme,args.mode):scoreThemeContrastBoth(resolved.theme),fails=results.filter((r)=>r.tier==="Fail").length,passes=results.length-fails;return jsonResult({theme:args.theme??"default",summary:{total:results.length,passes,fails},results})})}import{randomUUID as randomUUID3}from"crypto";import{appendFileSync,mkdirSync,writeFileSync}from"fs";import{join as join36}from"path";var TRACE_ENV="VELLOO_TRACE",MAX_PARAM_STR=16384;function traceEnabled(){let v=process.env[TRACE_ENV];return v!==void 0&&v!==""&&v!=="0"&&v.toLowerCase()!=="false"}function serializeError(err){if(err instanceof Error){let out={message:err.message};if(err.name)out.name=err.name;if(err.stack)out.stack=err.stack;return out}return{message:String(err)}}var SECRET_KEY=/^(cookies?|localstorage|storagestate|storagestatepath|authorization|auth|token|accesstoken|refreshtoken|apikey|api_key|password|secret|credentials?|sessionstate)$/i;function redactSecrets(value){if(Array.isArray(value))return value.map(redactSecrets);if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))out[k]=SECRET_KEY.test(k)?"<redacted>":redactSecrets(v);return out}return value}function clampStrings(value,max){if(typeof value==="string")return value.length>max?`${value.slice(0,max)}\u2026<elided ${value.length-max} chars>`:value;if(Array.isArray(value))return value.map((v)=>clampStrings(v,max));if(value&&typeof value==="object"){let out={};for(let[k,v]of Object.entries(value))out[k]=clampStrings(v,max);return out}return value}function extForMime(mime){if(mime.includes("jpeg")||mime.includes("jpg"))return"jpg";if(mime.includes("webp"))return"webp";if(mime.includes("gif"))return"gif";return"png"}class TraceRecorder{dir;seq=0;broken=!1;startedAt=new Map;pending=new Map;assetsDir;constructor(dir){this.dir=dir;this.assetsDir=join36(dir,"assets")}start(input){if(this.broken)return-1;let seq=this.seq++,call={seq,ts:new Date().toISOString(),tool:input.tool,params:clampStrings(redactSecrets(input.params),MAX_PARAM_STR)};if(input.sessionId)call.sessionId=input.sessionId;return this.pending.set(seq,call),this.startedAt.set(seq,Date.now()),this.writePending(),seq}end(seq,outcome){if(this.broken||seq<0)return;let call=this.pending.get(seq),startedAt=this.startedAt.get(seq);this.pending.delete(seq),this.startedAt.delete(seq),this.write(seq,{tool:call?.tool??"?",params:call?.params??null,durationMs:startedAt!==void 0?Date.now()-startedAt:0,...call?.sessionId?{sessionId:call.sessionId}:{},result:outcome.result,error:outcome.error}),this.writePending()}record(input){if(this.broken)return;this.write(this.seq++,{...input,params:redactSecrets(input.params)})}write(seq,input){if(this.broken)return;try{let rec=this.build(seq,input);appendFileSync(join36(this.dir,"tape.jsonl"),`${JSON.stringify(rec)}
|
|
441
|
+
`)}catch(err){this.broken=!0,console.error(`velloo trace: recording disabled after write failure: ${String(err)}`)}}writePending(){if(this.broken)return;try{writeFileSync(join36(this.dir,"pending.json"),JSON.stringify([...this.pending.values()]))}catch{}}build(seq,input){let rec={seq,ts:new Date().toISOString(),tool:input.tool,durationMs:Math.round(input.durationMs),ok:!0,params:clampStrings(input.params,MAX_PARAM_STR)};if(input.sessionId)rec.sessionId=input.sessionId;if(input.error!==void 0)return rec.ok=!1,rec.error=serializeError(input.error),rec;let{text,images,isError}=this.splitResult(seq,input.result);if(text!==void 0)rec.text=text;if(images.length)rec.images=images;if(isError)rec.isError=!0,rec.ok=!1;return rec}splitResult(seq,result){let images=[],texts=[],envelope=result,isError=envelope?.isError===!0,content=envelope?.content;if(Array.isArray(content)){let imgIdx=0;for(let item of content){if(!item||typeof item!=="object")continue;let type=item.type;if(type==="text"){let t=item.text;if(typeof t==="string")texts.push(t)}else if(type==="image"){let ref=this.writeImage(seq,imgIdx++,item);if(ref)images.push(ref)}}}return{text:texts.length?texts.join(`
|
|
442
|
+
`):void 0,images,isError}}writeImage(seq,idx,item){if(typeof item.data!=="string")return null;let mimeType=typeof item.mimeType==="string"?item.mimeType:"image/png",buf=Buffer.from(item.data,"base64"),file=`assets/${seq}-${idx}.${extForMime(mimeType)}`;return mkdirSync(this.assetsDir,{recursive:!0}),writeFileSync(join36(this.dir,file),buf),{file,mimeType,bytes:buf.byteLength}}}function newTapeId(){let d=new Date,p=(n)=>String(n).padStart(2,"0");return`${`${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`}-${randomUUID3().slice(0,4)}`}function createTraceRecorder(folderRoot){if(!traceEnabled())return null;let tapeId=newTapeId(),dir=join36(folderRoot,".velloo","trace",tapeId);try{mkdirSync(dir,{recursive:!0});let meta={tapeId,startedAt:new Date().toISOString(),folder:folderRoot,pid:process.pid};return writeFileSync(join36(dir,"meta.json"),`${JSON.stringify(meta,null,2)}
|
|
443
|
+
`),console.error(`velloo trace: recording MCP session to ${dir}`),new TraceRecorder(dir)}catch(err){return console.error(`velloo trace: could not start recorder: ${String(err)}`),null}}function withCallRecording(mcp,recorder){let original=mcp.registerTool.bind(mcp),patched=(name,config,cb)=>{let handler=cb;return original(name,config,async(args,extra)=>{let sessionId=extra?.sessionId,seq=recorder.start({tool:name,params:args,sessionId});try{let result=await handler(args,extra);return recorder.end(seq,{result}),result}catch(err){throw recorder.end(seq,{error:err}),err}})};return mcp.registerTool=patched,mcp}var INSTRUCTION_PARTS=["You are working on a Velloo design folder: a code-shaped design canvas whose components are the project's real component library. Designs are static \u2014 click handlers, routing and forms are no-ops.","","**The design folder is tool-owned.** Screens, boards, snippets and the theme live as JSON files inside it, but never read or edit those files by hand \u2014 every operation goes through these tools, which hold the write lock, validation and history. The user can watch the design render live with `velloo run`.","",'**Start by reading, once.** `list_components` (one namespace for components, extensions, and snippets \u2014 the default index groups families by what they are for and carries usage notes; `filter` to a family for its prop names, `mode: "full"` for examples), `component_status` for exact/adapted/fallback canvas fidelity when it matters, `get_theme` for the palette and tokens, `list_boards` for boards and frames. For an existing screen, `get_screen mode: "outline"` before pulling the full JSON.',"","**Use the library's own components \u2014 the catalog is larger than you expect.** Before building a pattern out of `Box` + `Text`, check the index for a family that already is it: a labelled input with help and error text is `Field`, a search box with an inline icon is `InputGroup`, a settings/file row is `Item`, a \"nothing here yet\" panel is `Empty`, joined buttons are `ButtonGroup`. A family listing `pieces` is composed of them (`Field` \u21D2 `FieldLabel` / `FieldDescription` / `FieldError`). Hand-rolling one of these loses the library's spacing, states and dark-mode behavior, and `emit_code` then hands the developer a div stack instead of the component their app already has.","","**Three customization layers** stack additively:"," - **Libraries** are the baseline component palette. A folder registers N (`config.libraries`); each screen pins one via `screen.library`, and component ids resolve against that library only."," - **Extensions** add wholly new components the library doesn't have \u2014 the app's own `DataTable`, a brand `Hero`. Register with `add_extension`; they emit a real import. Additive customization, NOT compositions."," - **Snippets** compose existing components into named subtrees with typed params \u2014 the tool for repeated structure (FeatureCard, NavRow, PricingTier). `compose` resolves their PascalCase names in the same JSX tag namespace as components and extensions.","","**Styling is framework-native.** `update_props`'s `style` channel routes your payload to whatever the screen's framework uses \u2014 a Tailwind `className` string, an `sx` object, or a plain `style` object \u2014 so one verb works everywhere. Prefer theme tokens over hard-coded values in any channel; on a Tailwind folder prefer semantic tokens (`bg-background`, `text-muted-foreground`, `bg-primary`) over raw palette colors, because only semantic tokens theme-flip in dark mode.","","**EFFICIENCY \u2014 build in big strokes, read once.** A screen should take a few dozen tool calls, not hundreds; over-calling is the most common failure. (1) **Compose whole subtrees** \u2014 `compose` accepts familiar nested JSX, so build a whole feature card in ONE call rather than node-by-node. (2) **`batch` groups a sequence of metadata/property mutations into one round-trip**, atomic by default: on the first error every touched resource rolls back and the result reports `rolledBack: true` with the failing call. (3) **Work from memory** \u2014 do NOT re-`get_screen` or re-`list_boards` before every edit; to re-locate a node use `find_nodes`, which returns its path. (4) **Don't thrash** \u2014 plan the structure before building it, and edit a snippet through `update_snippet`'s `innerPatch` rather than redefining its body.","",'**Think in ids, not paths.** Anywhere a tool asks for a `path` (or `parentPath`, `fromPath`, `toParent`), pass a stable id reference like `"@hero-cta"`. Assign ids at creation (`id: "hero-cta"`) for anything you might touch again. Number paths are positional and break when siblings move; treat them as an implementation detail you get from `find_nodes` (`set_node_id` retrofits one).',"",'**Verify before declaring done.** Mutations return focused class, theme, and render diagnostics \u2014 a `render/component-threw` diagnostic at some path means the canvas shows a placeholder there instead of the component you asked for, and the message names what it needs (usually a parent it must sit inside); `screenshot mode: "compare"` renders light and dark side by side and returns a full-screen diagnostic pass. `emit_code` repeats that full check at the handoff boundary. See velloo://guide/verification.',"","**Make it distinctive.** Default library + Inter + one indigo reads as template. Set a display face and a typeset early via `set_theme` \u2014 one call re-proportions every screen \u2014 then reach for real art and confident color. See velloo://guide/art.","","**Read the guide before doing the thing.** The advertised `velloo://guide/*` resources carry the detail this brief deliberately omits; fetch the relevant one before using an unfamiliar capability."],GUIDED_INSTRUCTION_PARTS=["You are working on a Velloo design folder: a code-shaped design canvas backed by the project's real component library.","","**The design folder is tool-owned.** Never read or edit its JSON files by hand. Use `operation_schema` before an unfamiliar operation, `call_velloo` for one native operation, and `run_velloo_plan` for up to eight related calls.","","Build in large strokes with `compose` and `batch`, keep stable node ids, prefer theme tokens, and avoid repeatedly re-reading unchanged state. Use `component_status` before claiming an app component renders exactly. Verify design work with `screenshot`; use `compare_to_url` for code-to-design fidelity and `emit_code` at implementation handoff.","","The operation enum is the whole catalogue available to this session. Failed fa\xE7ade calls include the exact native operation schema needed to correct them.","","**Read the guide before doing the thing.** The `velloo://guide/*` resources carry the detail this brief omits \u2014 verification, theming, porting, art direction, comment threads. Fetch the relevant one before using an unfamiliar capability."],FEEDBACK_INSTRUCTION="**Sending product feedback**: this folder opted into the `send_feedback` tool. Reach for it when you hit friction with **Velloo itself** \u2014 a confusing instruction, a missing capability, a tool that misbehaved, a bug \u2014 or when the user asks to send feedback. ALWAYS show the user the exact `body` and get their go-ahead before calling; never send unprompted, even when you originated the idea. Fire sparingly \u2014 one report per distinct issue, never repeated. NEVER include the user's design content, code, or file/repo paths; describe the issue in your own words. This is feedback about Velloo, not about the design.";function buildInstructions(feedbackEnabled,canvasUrl,intro=[],openComments=0,hostTailwindMajor=null,bareFolder=!1,surface={mode:"full"},designs=null){let parts=[...intro,...surface.mode==="guided"?GUIDED_INSTRUCTION_PARTS:INSTRUCTION_PARTS];if(bareFolder)parts.unshift("**Bare folder.** This design has no boards yet. Setup order before composing UI: (1) style the theme with `set_theme` (or `import_theme` to match an existing app); (2) `add_board`; (3) add screens and frames, then design.","");if(designs)parts.unshift(...designsInstruction(designs),"");if(hostTailwindMajor===3)parts.push("","**The host app is on Tailwind v3** (the canvas itself always compiles v4). Prefer classes spelled the same in both majors; avoid v4-only utilities (`inset-shadow-*`, `text-shadow-*`, `bg-linear-*` angles, container-query variants, `starting:`). Mutations flag incompatible classes at their paths, and `emit_code` returns a `tailwindV3Compat` rename list (e.g. v4 `shadow-sm` \u21D2 v3 `shadow`) to apply when writing app code. `emit_theme` detects the v3 target and emits `velloo-theme.css` + a `velloo.preset` instead of a v4 globals.css.");if(canvasUrl)parts.push("",`**The live canvas** is running at ${canvasUrl} \u2014 give the user this URL up front so they can open it and watch your edits render in real time. (They can also open a canvas any time with \`velloo run\`.)`);if(openComments>0){let one=openComments===1;parts.push("",`**${one?"1 open visual feedback thread is":`${openComments} open visual feedback threads are`} waiting on you.** ${one?"It is a change":"Each one is a change"} the user is expecting. Read ${one?"it":"them"} with the \`list_comment_threads\` operation, make the requested ${one?"change":"changes"}, then reply and resolve with \`update_comment_thread\`. The full loop is velloo://guide/comments.`)}if(feedbackEnabled)parts.push("",FEEDBACK_INSTRUCTION);return parts.join(`
|
|
444
|
+
`)}function buildMcpServer(ctx,jit,bundler,canvasBundler,comments,assetOrigin,cloud,surface=DEFAULT_MCP_SURFACE,designs=null){let feedbackEnabled=Boolean(ctx.folder.config.feedback?.enabled&&cloud?.url),channel=styleChannelOf(ctx.defaultProvider,ctx.folder.config.styling?.framework),channelKind=channel.kind,intro=ctx.defaultProvider.mcpIntro?.(channelKind)??[],hostTailwindMajor=channel.needsTailwindJit?detectTailwindMajor(hostAppRootFrom(ctx.folder.root,ctx.folder.config.hostApp)):null,openComments=comments.countOpenSync(),bareFolder=ctx.folder.boards.size===0,mcp=new McpServer(MCP_SERVER_INFO,{instructions:buildInstructions(feedbackEnabled,assetOrigin&&trimTrailingSlashes(assetOrigin),intro,openComments,hostTailwindMajor,bareFolder,surface,designs)}),toolSurface=applyMcpToolSurface(mcp,surface);applyToolPolicy(mcp);let recorder=createTraceRecorder(ctx.folder.root);if(recorder)withCallRecording(mcp,recorder);if(registerDiscoveryTools(mcp,ctx),registerComposeTool(mcp,ctx,jit),registerMutationTools(mcp,ctx,jit),registerInspectTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerThemeTools(mcp,ctx),registerEmitTools(mcp,ctx,jit),registerScreenshotTool(mcp,ctx,jit,bundler,canvasBundler,assetOrigin),registerExtensionTools(mcp,ctx),registerNoteTools(mcp,ctx),registerAssetTools(mcp,ctx),registerBatchTool(mcp,ctx,jit),registerCaptureTools(mcp,ctx),feedbackEnabled&&cloud)registerFeedbackTool(mcp,ctx,cloud);if(registerCommentTools(mcp,comments),registerGenerateTools(mcp,ctx,cloud??{url:""}),designs)registerDesignTools(mcp,ctx,designs);return toolSurface.finish(),registerGuideResources(mcp),mcp}async function readBody2(req){let chunks=[];for await(let chunk of req)chunks.push(chunk);if(chunks.length===0)return;let raw=Buffer.concat(chunks).toString("utf8");if(raw.length===0)return;return JSON.parse(raw)}function isInitializeRequest2(body){if(!body||typeof body!=="object")return!1;return(Array.isArray(body)?body:[body]).some((m)=>m&&typeof m==="object"&&m.method==="initialize")}function trimTrailingSlashes(url){let end=url.length;while(end>0&&url[end-1]==="/")end--;return url.slice(0,end)}function sendJson(res,status,value){res.statusCode=status,res.setHeader("Content-Type","application/json"),res.end(JSON.stringify(value))}async function createMcpServer(ctx,opts){let sessions=new Map,httpServer=createHttpServer(async(req,res)=>{if(!requestIsLocal({host:req.headers.host,origin:req.headers.origin})){sendJson(res,403,{error:"forbidden: request is not from a local origin"});return}if(!req.url?.startsWith("/mcp")){sendJson(res,404,{error:"expected /mcp"});return}let sessionId=req.headers["mcp-session-id"],sessionIdStr=Array.isArray(sessionId)?sessionId[0]:sessionId;try{if(req.method==="POST"){let body=await readBody2(req),session=sessionIdStr?sessions.get(sessionIdStr):void 0;if(!session){if(!isInitializeRequest2(body)){sendJson(res,400,{error:"missing or unknown mcp-session-id; send `initialize` first"});return}let transport=new StreamableHTTPServerTransport({sessionIdGenerator:()=>randomUUID4(),enableJsonResponse:!0,onsessioninitialized:(id)=>{sessions.set(id,{transport,server})}}),parsedSurface=parseMcpSurfaceUrl(req.url);if(!parsedSurface.ok){sendJson(res,400,{error:parsedSurface.error});return}let server=buildMcpServer(ctx,opts.jit,opts.bundler,opts.canvasBundler,opts.comments,opts.assetOrigin,opts.cloud,parsedSurface.selection,await sessionDesigns(ctx.folder.root,ctx.folder.config.name,parseMcpSessionUrl(req.url)));transport.onclose=()=>{if(transport.sessionId)sessions.delete(transport.sessionId);server.close().catch(()=>{return})},await server.connect(transport),session={transport,server}}let active=session;await withActor({source:"mcp",session:sessionIdStr??active.transport.sessionId},()=>active.transport.handleRequest(req,res,body));return}if(req.method==="GET"||req.method==="DELETE"){if(!sessionIdStr||!sessions.has(sessionIdStr)){sendJson(res,400,{error:"missing or unknown mcp-session-id"});return}let session=sessions.get(sessionIdStr);if(!session){sendJson(res,400,{error:"session lost"});return}await session.transport.handleRequest(req,res);return}sendJson(res,405,{error:`method ${req.method} not allowed`})}catch(err){if(console.error("velloo mcp: handler failed:",err),!res.headersSent)sendJson(res,500,{error:"internal error"})}});await new Promise((resolve,reject)=>{httpServer.once("error",reject),httpServer.listen(opts.port,opts.host,()=>{httpServer.removeListener("error",reject),resolve()})});let addr=httpServer.address(),boundPort=typeof addr==="object"&&addr!==null&&"port"in addr?addr.port:opts.port;return{url:`http://${opts.host}:${boundPort}/mcp`,port:boundPort,sessions:()=>sessions.size,async close(){for(let session of sessions.values())await session.transport.close().catch(()=>{return}),await session.server.close().catch(()=>{return});sessions.clear(),await new Promise((resolve)=>httpServer.close(()=>resolve()))}}}async function createStdioMcpServer(ctx,opts){let server=buildMcpServer(ctx,opts.jit,opts.bundler,opts.canvasBundler,opts.comments,opts.assetOrigin,opts.cloud,opts.surface??DEFAULT_MCP_SURFACE,await sessionDesigns(ctx.folder.root,ctx.folder.config.name,{switchable:!1,pick:void 0})),transport=new StdioServerTransport;return await server.connect(transport),{async close(){await transport.close().catch(()=>{return}),await server.close().catch(()=>{return})}}}class PublishRunner{publisher;host;current={state:"idle"};warnings=[];constructor(publisher,host){this.publisher=publisher;this.host=host}state(){return this.current}running(){return this.current.state==="running"}teams(){return this.publisher.teams()}destinations(){return this.publisher.destinations(this.host())}access(){return this.publisher.access()}published(){return this.publisher.published()}unpublish(slug){return this.publisher.unpublish(slug)}start(request){if(this.current.state==="running")return null;let startedAt=new Date().toISOString();return this.warnings=[],this.current={state:"running",step:"start",message:"starting",startedAt,warnings:this.warnings},this.publisher.run(this.host(),request,({step,message,capture})=>{if(this.current.state!=="running"||this.current.startedAt!==startedAt)return;this.current={state:"running",step,message,startedAt,warnings:this.warnings,...capture?{capture}:{}}},(message)=>{this.warnings.push(message)}).then((result)=>{this.current={state:"done",result,warnings:this.warnings,finishedAt:new Date().toISOString()}}).catch((err)=>{let access=asSignInRequired(err),boardLimit=asBoardLimit(err);this.current={state:"error",message:err instanceof Error?err.message:String(err),...access?{signInRequired:access}:{},...boardLimit?{boardLimit}:{},warnings:this.warnings,finishedAt:new Date().toISOString()}}),this.current}reset(){if(this.current.state!=="running")this.current={state:"idle"}}}import{existsSync as existsSync10,readdirSync as readdirSync3,statSync,watch}from"fs";import{join as join37,sep as sep3}from"path";function classifyWatchPath(filename){if(!filename)return null;let parts=filename.split(sep3);if(parts[0]==="screens"&&parts[1]){let file=parts[1];if(file.endsWith(".annotations.json"))return{type:"annotations-changed",screenId:file.slice(0,-17)};if(file.endsWith(".json")){let stem=file.slice(0,-5);if(!stem.includes("."))return{type:"screen-changed",screenId:stem}}}if(parts[0]==="boards"&&parts[1]){let file=parts[1];if(file.endsWith(".notes.json"))return{type:"notes-changed",boardId:file.slice(0,-11)};if(file.endsWith(".json")){let stem=file.slice(0,-5);if(!stem.includes("."))return{type:"board-changed",boardId:stem}}}if(parts[0]==="theme"&&parts[1]&&/\.(json|css)$/.test(parts[1]))return{type:"theme-changed"};if(parts[0]==="snippets"&&parts[1]&&parts[1].endsWith(".json"))return{type:"snippet-changed",snippetId:parts[1].slice(0,-5)};return null}function watchDesignFolder(root,onEvent,debounceMs=50){let pending=new Map,watchers=[],fingerprint=designFingerprint(root);function schedule(key,ev){let prev=pending.get(key);if(prev)clearTimeout(prev);pending.set(key,setTimeout(()=>{pending.delete(key),onEvent(ev)},debounceMs))}for(let sub of["screens","boards","theme","snippets"])try{let w=watch(join37(root,sub),(_eventType,filename)=>{if(!filename)return;let rel=`${sub}${sep3}${filename}`,ev=classifyWatchPath(rel);if(ev)fingerprint=designFingerprint(root),schedule(rel,ev)});watchers.push(w)}catch{}let poll=setInterval(()=>{let next=designFingerprint(root),paths=new Set([...fingerprint.keys(),...next.keys()]);for(let path of paths){if(fingerprint.get(path)===next.get(path))continue;let event=classifyWatchPath(path);if(event)schedule(path,event)}fingerprint=next},250);return{close(){for(let w of watchers)w.close();clearInterval(poll);for(let t of pending.values())clearTimeout(t);pending.clear()}}}function designFingerprint(root){let out=new Map;for(let sub of["screens","boards","theme","snippets"]){let dir=join37(root,sub);if(!existsSync10(dir))continue;for(let name of readdirSync3(dir)){let path=join37(dir,name);try{let stat=statSync(path);if(stat.isFile())out.set(`${sub}${sep3}${name}`,`${stat.mtimeMs}:${stat.size}`)}catch{}}}return out}function watchSourcePaths(paths,onChange,debounceMs=80){let targets=[...new Set(paths)],watchers=new Map,timer,schedule=()=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>{timer=void 0,onChange()},debounceMs)},arm=(target)=>{if(watchers.has(target))return;try{if(!statSync(target).isDirectory())return;watchers.set(target,watch(target,{recursive:!0},(_event,filename)=>{if(filename)schedule()}))}catch{}},disarm=(target)=>{watchers.get(target)?.close(),watchers.delete(target)};for(let target of targets)arm(target);let fingerprint=sourceFingerprint(targets),poll=setInterval(()=>{let next=sourceFingerprint(targets);if(next===fingerprint)return;fingerprint=next;for(let target of targets)if(existsSync10(target))arm(target);else disarm(target);schedule()},250);return{close(){for(let watcher of watchers.values())watcher.close();if(watchers.clear(),clearInterval(poll),timer)clearTimeout(timer)}}}function sourceFingerprint(paths){let rows=[],visit=(path)=>{try{if(!existsSync10(path)){rows.push(`${path}:missing`);return}let stat=statSync(path);if(rows.push(`${path}:${stat.mtimeMs}:${stat.size}`),!stat.isDirectory())return;for(let name of readdirSync3(path).sort())visit(join37(path,name))}catch{rows.push(`${path}:raced`)}};for(let path of paths)visit(path);return rows.join("|")}var MIME2={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".woff2":"font/woff2",".ico":"image/x-icon"};async function serveStatic(req,canvasDist){let url=new URL(req.url),requested=url.pathname==="/"?"/index.html":url.pathname,fsPath=join38(canvasDist,requested);if(!(fsPath===canvasDist||fsPath.startsWith(canvasDist+sep4))||!existsSync11(fsPath))return null;let file=Bun.file(fsPath);if(!await file.exists())return null;let type=MIME2[extname3(fsPath)]??"application/octet-stream";return new Response(file,{headers:{"Content-Type":type}})}function extraThemeBlock(folder){let fonts={},palette={},spacing={},shadows={},animation={},keyframes={};for(let theme of folder.themes.values()){for(let[role,stack]of Object.entries(theme.typography.fontFamily??{}))fonts[role]=stack;for(let[name,value]of Object.entries(theme.palette??{}))palette[name]=value;for(let[name,value]of Object.entries(theme.paletteDark??{}))palette[name]??=value;for(let[name,value]of Object.entries(theme.spacing??{})){if(!Number.isNaN(Number(name)))continue;if(typeof value==="string"||typeof value==="number")spacing[name]=String(value)}for(let[name,value]of Object.entries(theme.shadows??{}))if(typeof value==="string"||typeof value==="number")shadows[name]=String(value);for(let[name,value]of Object.entries(theme.animation??{}))animation[name]=value;for(let[name,steps]of Object.entries(theme.keyframes??{}))keyframes[name]=steps}let lines=[...Object.entries(fonts).filter(([role])=>isCssIdent(role)).map(([role,stack])=>` --font-${role}: ${sanitizeCssTokenValue(stack)};`),...Object.entries(palette).map(([name,value])=>` --color-${name}: ${value};`),...Object.entries(spacing).map(([name,value])=>` --spacing-${name}: ${value};`),...Object.entries(shadows).map(([name,value])=>` --shadow-${name}: ${value};`),...Object.entries(animation).map(([name,value])=>` --animate-${name}: ${value};`),...typesetThemeTokens()],keyframesCss=keyframesToCss(Object.keys(keyframes).length>0?keyframes:void 0," "),safelist=typesetSafelist().map((pattern)=>`@source inline("${pattern}");`).join(`
|
|
445
445
|
`),themeBlock=lines.length===0&&keyframesCss===""?"":`@theme {
|
|
446
446
|
${[...lines,keyframesCss].filter(Boolean).join(`
|
|
447
447
|
`)}
|
|
448
448
|
}`;return[safelist,themeBlock].filter(Boolean).join(`
|
|
449
449
|
|
|
450
|
-
`)}async function serveFolderAsset(req,folderRoot){let url=new URL(req.url);if(!url.pathname.startsWith("/assets/"))return null;let fsPath=
|
|
451
|
-
export{clsx,clsx_default,cn,Box,helperDescriptors,require_react_jsx_dev_runtime_development,Divider,Gradient,Heading,Icon,Image2,Layer,Placeholder,Prose,SVG,Text,helpersRegistry,detectTailwindMajor,colorizeDiff,emitCode,moduleTarget,converter_default,parse_default,formatHex,formatRgb,emitTheme,emitNativeTheme,parseThemeCss,v3ClassIssues,classNamesInJsx,signInRequired,boardLimitReached,writeJsonAtomic,writeText,TAILWIND_CLASSNAME,SX_PROP,STYLE_PROP,catalogFromManifest,identifierRef,styleChannelOf,managedDesignPath,managedDesignId,listLocalDesigns,
|
|
450
|
+
`)}async function serveFolderAsset(req,folderRoot){let url=new URL(req.url);if(!url.pathname.startsWith("/assets/"))return null;let fsPath=join38(folderRoot,decodeURIComponent(url.pathname));if(!fsPath.startsWith(join38(folderRoot,"assets")+sep4))return null;let file=Bun.file(fsPath);if(!await file.exists())return null;let assetType=ASSET_MIME[extname3(fsPath).toLowerCase()],headers={"Content-Type":assetType??"application/octet-stream","X-Content-Type-Options":"nosniff"};if(!assetType)headers["Content-Disposition"]="attachment";if(assetType==="image/svg+xml")headers["Content-Security-Policy"]="script-src 'none'";return new Response(file,{headers})}async function serveNonApi(req,folderRoot,canvasDist){let assetResponse=await serveFolderAsset(req,folderRoot);if(assetResponse)return assetResponse;let staticResponse=canvasDist?await serveStatic(req,canvasDist):null;if(staticResponse)return staticResponse;if(new URL(req.url).pathname.startsWith("/assets/"))return new Response("asset not found",{status:404,headers:{"Content-Type":"text/plain; charset=utf-8"}});if(req.method==="GET")return serveSpaFallback(canvasDist);return new Response("Not found",{status:404})}async function serveSpaFallback(canvasDist){let file=canvasDist?Bun.file(join38(canvasDist,"index.html")):null;if(file&&await file.exists())return new Response(file,{headers:{"Content-Type":"text/html; charset=utf-8"}});return new Response("<!doctype html><h1>Velloo canvas not built</h1><p>Run <code>bun run --cwd packages/canvas build</code> from the repo root.</p>",{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}var COMMENT_SYNC_INTERVAL_MS=300000;async function createServer(opts){let folder=await loadDesignFolder(opts.folder),{providers,defaultProvider}=await resolveProviders(folder.config,folder.root),broadcaster=new Broadcaster,bundler=new LiveBundler(folder.root,()=>folder.config,()=>liveExtensions(folder.config.extensions)),canvasBundler=new CanvasBundler(folder.root,()=>folder.config.hostApp,(libraryId)=>providers[libraryId]?.canvasBundleSpec),jit=new TailwindJit(Object.values(providers),join38(folder.root,"screens"),void 0,()=>extraThemeBlock(folder),()=>[...bundler.hostSourceDirs(),...canvasBundler.sourceDirs(Object.keys(providers))],()=>findHostTailwindConfig(folder.root,folder.config.hostApp),folder.config.styling?.framework),broadcast=(e)=>{if(e.type==="screen-changed"||e.type==="theme-changed"||e.type==="snippet-changed")jit.invalidate();if(e.type==="config-changed"||e.type==="folder-reloaded")bundler.invalidate(),canvasBundler.invalidate(),jit.invalidate();broadcaster.broadcast(e)},ctx={folder,providers,defaultProvider,canvasBundler,broadcast},publishRunner=opts.publish?new PublishRunner(opts.publish,()=>({folder,providers,defaultProvider,snapshotCss:()=>jit.build()})):void 0,comments=new LocalCommentsService(()=>ctx,void 0,opts.cloud,publishRunner),app=createApp(()=>ctx,jit,bundler,canvasBundler,opts.auth,publishRunner,opts.cloud,comments,opts.updates),watcher=null,sourceWatcher=watchSourcePaths([...bundler.hostSourceDirs(),...canvasBundler.sourceDirs(Object.keys(providers))],()=>broadcast({type:"folder-reloaded"}));watcher=watchDesignFolder(folder.root,async(event)=>{try{if(event.type==="screen-changed")await reloadScreen(folder,event.screenId);else if(event.type==="board-changed")await reloadBoard(folder,event.boardId);else if(event.type==="theme-changed")await reloadTheme(folder);else if(event.type==="snippet-changed")await reloadSnippet(folder,event.snippetId);else if(event.type==="annotations-changed")await reloadAnnotations(folder,event.screenId);else if(event.type==="notes-changed")await reloadNotes(folder,event.boardId);broadcast(event)}catch(err){console.error("velloo: failed to reload after change:",err),broadcast({type:"reload-error",source:event.type,message:err instanceof Error?err.message:String(err)})}});let server=Bun.serve({port:opts.port??7300,hostname:opts.host??"127.0.0.1",async fetch(req,srv){let url=new URL(req.url);if(url.pathname==="/ws"){if(!requestIsLocal({host:url.host,origin:req.headers.get("origin")}))return new Response("forbidden",{status:403});if(srv.upgrade(req,{data:{}}))return;return new Response("expected websocket upgrade",{status:400})}if(url.pathname.startsWith("/api/"))return app.fetch(req);if(!hostIsLoopback(url.host))return new Response("forbidden",{status:403});return serveNonApi(req,folder.root,opts.canvasDist)},websocket:{open(ws){broadcaster.register(ws)},close(ws){broadcaster.unregister(ws)},message(){}}}),port=server.port??opts.port??7300,assetOrigin=`http://${opts.host??"127.0.0.1"}:${port}/`,commentTimer=null;if(opts.cloud){let sync=async()=>{try{await comments.refreshShared()}catch{}};sync(),commentTimer=setInterval(()=>void sync(),COMMENT_SYNC_INTERVAL_MS)}let httpMcp,stdioMcp;if(opts.mcp?.transport==="http")httpMcp=await createMcpServer(ctx,{port:opts.mcp.port??7301,host:opts.host??"127.0.0.1",jit,bundler,canvasBundler,comments,assetOrigin,cloud:opts.cloud});else if(opts.mcp?.transport==="stdio")stdioMcp=await createStdioMcpServer(ctx,{jit,bundler,canvasBundler,comments,assetOrigin,cloud:opts.cloud});return{url:`http://${server.hostname}:${port}`,port,mcpUrl:httpMcp?.url,mcpPort:httpMcp?.port,connections:()=>({canvas:broadcaster.size(),mcp:httpMcp?.sessions()??0}),async close(){if(commentTimer)clearInterval(commentTimer);watcher?.close(),sourceWatcher.close(),await httpMcp?.close(),await stdioMcp?.close(),server.stop(!0)}}}
|
|
451
|
+
export{clsx,clsx_default,cn,Box,helperDescriptors,require_react_jsx_dev_runtime_development,Divider,Gradient,Heading,Icon,Image2,Layer,Placeholder,Prose,SVG,Text,helpersRegistry,detectTailwindMajor,colorizeDiff,emitCode,moduleTarget,converter_default,parse_default,formatHex,formatRgb,emitTheme,emitNativeTheme,parseThemeCss,v3ClassIssues,classNamesInJsx,signInRequired,boardLimitReached,writeJsonAtomic,writeText,TAILWIND_CLASSNAME,SX_PROP,STYLE_PROP,catalogFromManifest,identifierRef,styleChannelOf,managedDesignPath,managedDesignId,listLocalDesigns,localDesignOf,writeLocalDesign,removeLocalDesign,resolveAppPath,readRepoManifestAt,findOwningManifest,recordedDesignName,findDesigns2,pickDesign,designRenameConflict,followDesignRename,readRepoFeedback,writeRepoFeedback,writeFeedbackContactOk,loadDesignFolder,orderedBoards,activeBoards,registryForScreen,renderPassForScreen,findFrame,exportFilename,exportFramePng,exportBoardPng,exportFramePdf,exportBoardPdf,exportScreenHtml,exportScreenPng,exportScreenPdf,exportFrameHtml,exportBoardHtml,hostAppRootFrom,liveExtensions,LiveBundler,preflightScreens,screensForBoards,screensForExportTarget,derivePalette,helperSourcePath,withMcpSessionUrl,MCP_SURFACE_MODES,parseMcpSurfaceSelection,withMcpSurfaceUrl,topUpTokens,resolveProviders,findHostTailwindConfig,TailwindJit,runStdioFormatGate,runStdioMcpProxy,extraThemeBlock,createServer};
|