velloo 0.4.1 → 0.5.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 +29 -7
- package/canvas/assets/index-BDBBHdHH.css +2 -0
- package/canvas/assets/index-QdsLJNY9.js +29 -0
- package/canvas/assets/{lucide-all-Bcm80WFX.js → lucide-all-R8DUyY-B.js} +1 -1
- package/canvas/index.html +2 -2
- package/chunk-0rdf6trc.js +2 -0
- package/{chunk-npsg7btt.js → chunk-0wafc9aw.js} +1 -1
- package/chunk-0wqxb8r9.js +3 -0
- package/{chunk-e6nhc3ay.js → chunk-10hap8kc.js} +1 -1
- package/chunk-18eqh3s1.js +2 -0
- package/chunk-19xyzep8.js +2 -0
- package/{chunk-7gtvstne.js → chunk-1h3gqy7w.js} +1 -1
- package/{chunk-vjrn10xr.js → chunk-33t0csxs.js} +2 -2
- package/{chunk-7xffbj5h.js → chunk-3ffs39e8.js} +1 -1
- package/chunk-42bj6r30.js +3 -0
- package/chunk-49w26e94.js +5 -0
- package/{chunk-7a45tn73.js → chunk-4pkw6d9n.js} +2 -2
- package/{chunk-ph94nrc8.js → chunk-4sjvdakg.js} +1 -1
- package/chunk-5f0xv80x.js +5 -0
- package/{chunk-2dm4x17z.js → chunk-5ma8zbty.js} +1 -1
- package/{chunk-t9ygewjx.js → chunk-5y9hem1j.js} +1 -1
- package/chunk-66qgm91k.js +708 -0
- package/{chunk-dj1pp4e3.js → chunk-675tr675.js} +1 -1
- package/{chunk-jwzhhhzn.js → chunk-6cgvzwec.js} +1 -1
- package/chunk-6k05kqnc.js +3 -0
- package/{chunk-py6sjyzk.js → chunk-6ytv1v2r.js} +1 -1
- package/{chunk-2h24va0j.js → chunk-75sc3adm.js} +1 -1
- package/chunk-7cexzg71.js +2 -0
- package/chunk-98rann8t.js +2 -0
- package/{chunk-b58pnj20.js → chunk-a16mz9zf.js} +1 -1
- package/{chunk-m1wp8cpt.js → chunk-afe25dpr.js} +1 -1
- package/{chunk-k3dpwjx4.js → chunk-ahm4zd56.js} +1 -1
- package/chunk-asvdqkdp.js +6 -0
- package/{chunk-rfrbnw8r.js → chunk-b3x80jc1.js} +1 -1
- package/{chunk-tr49kraa.js → chunk-cdqah1hy.js} +1 -1
- package/{chunk-r03aywsb.js → chunk-cqt1dpxd.js} +1 -1
- package/{chunk-xbhxzdat.js → chunk-d4r0myxr.js} +1 -1
- package/{chunk-km3z2czq.js → chunk-d9kx7v12.js} +1 -1
- package/{chunk-2y9nnzpr.js → chunk-df557bn4.js} +1 -1
- package/{chunk-6zr596r2.js → chunk-edgwx026.js} +1 -1
- package/chunk-emv1j50t.js +2 -0
- package/{chunk-jep31ffe.js → chunk-ewgdcgrk.js} +1 -1
- package/chunk-f45p6kcb.js +2 -0
- package/{chunk-b4jy4hcd.js → chunk-gn9wcc1b.js} +1 -1
- package/{chunk-cejmmx3r.js → chunk-hr3gbak5.js} +2 -2
- package/{chunk-91pajfnm.js → chunk-j7tzbt3d.js} +1 -1
- package/chunk-mm8wv3bg.js +3 -0
- package/chunk-nbbnb5e9.js +3 -0
- package/{chunk-a9mgvspm.js → chunk-ngbqd7kb.js} +32 -12
- package/chunk-nkqtnwzz.js +2 -0
- package/{chunk-2jbhv7vh.js → chunk-q3kf8243.js} +1 -1
- package/{chunk-8t5phg0s.js → chunk-qpv28z8d.js} +1 -1
- package/{chunk-hsrg23gd.js → chunk-rd65xm6r.js} +1 -1
- package/{chunk-aa3str7g.js → chunk-ryga3ny1.js} +1 -1
- package/{chunk-cvn689d5.js → chunk-sb5h27w9.js} +2 -2
- package/chunk-tw4kw4ps.js +22 -0
- package/chunk-v6c3h9ft.js +3 -0
- package/chunk-vgx47anc.js +3 -0
- package/chunk-vrvkvb0v.js +244 -0
- package/{chunk-db7mf114.js → chunk-vryadew3.js} +9 -7
- package/chunk-w6f40y2x.js +11 -0
- package/{chunk-0446t32z.js → chunk-we8hvy6e.js} +1 -1
- package/{chunk-ykdx12na.js → chunk-wkqrb2tz.js} +1 -1
- package/{chunk-r9ekd92e.js → chunk-xzkczmgg.js} +1 -1
- package/{chunk-yfttm20w.js → chunk-ycrddh47.js} +1 -1
- package/{chunk-j5axqyan.js → chunk-z78c1kf8.js} +1 -1
- package/cli.js +1 -1
- package/package.json +3 -1
- package/pkgs/provider-html/src/index.ts +68 -0
- package/pkgs/provider-html/src/registry.ts +121 -0
- package/pkgs/provider-html/src/version.ts +1 -0
- package/pkgs/provider-none/src/components-inline.tsx +12 -3
- package/pkgs/provider-none/src/components.tsx +11 -3
- package/pkgs/provider-none/src/manifest.ts +1 -0
- package/pkgs/schema/src/config.ts +33 -1
- package/pkgs/schema/src/css-declarations.ts +41 -0
- package/pkgs/schema/src/index.ts +3 -0
- package/pkgs/schema/src/theme.ts +40 -0
- package/plugins/claude/velloo/agents/velloo-design-reviewer.md +56 -13
- package/plugins/claude/velloo/agents/velloo-designer.md +4 -1
- package/skills/velloo-setup/SKILL.md +19 -55
- package/canvas/assets/index-B1OD0Yz5.css +0 -2
- package/canvas/assets/index-BWYFx2Rr.js +0 -29
- package/chunk-00t0bz7x.js +0 -244
- package/chunk-0zen7bzb.js +0 -3
- package/chunk-5ywkk7d6.js +0 -2
- package/chunk-b7famda5.js +0 -3
- package/chunk-f5g45xed.js +0 -2
- package/chunk-fjbzk4sw.js +0 -3
- package/chunk-hv52a6hf.js +0 -2
- package/chunk-mcymwefw.js +0 -5
- package/chunk-n0kx20s2.js +0 -2
- package/chunk-nwf2kn41.js +0 -26
- package/chunk-phz993p3.js +0 -2
- package/chunk-qan5dnjt.js +0 -688
- package/chunk-r5d0jsnd.js +0 -5
- package/chunk-shac6bat.js +0 -6
- package/chunk-t4zvcece.js +0 -10
- package/chunk-tarws372.js +0 -2
- package/chunk-yghypth2.js +0 -2
- package/chunk-zqtz23xk.js +0 -3
package/canvas/index.html
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
<link rel="manifest" href="/site.webmanifest" />
|
|
10
10
|
<meta name="theme-color" content="#0d0c0a" />
|
|
11
11
|
<title>velloo</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-QdsLJNY9.js"></script>
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-D0Ec-185.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/vendor-CKmQ-Zst.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/radix-gmpYzWRH.js">
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BDBBHdHH.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{isCancel,select}from"./chunk-80fjfyha.js";import{closePooledBrowser}from"./chunk-ngbqd7kb.js";import"./chunk-4pkw6d9n.js";import{writeText,findFrame,exportFilename,exportFramePng,exportBoardPng,exportFramePdf,exportBoardPdf,exportScreenHtml,exportScreenPng,exportScreenPdf,exportFrameHtml,exportBoardHtml,hostFilesFetch,screensForExportTarget}from"./chunk-66qgm91k.js";import"./chunk-afe25dpr.js";import{fail}from"./chunk-s23gtejf.js";import{captureWithBrowserSetup}from"./chunk-5ma8zbty.js";import{confirmRenderFailures}from"./chunk-j7tzbt3d.js";import{loadPipeline}from"./chunk-6cgvzwec.js";import{withAssetServer}from"./chunk-vgx47anc.js";import{DESIGN_ARG_DESCRIPTION,resolveDesign}from"./chunk-675tr675.js";import{defineCommand}from"./chunk-2smsg2ct.js";import{extname,isAbsolute,resolve}from"path";var export_default=defineCommand({meta:{name:"export",description:"Export a screen, frame, or board as PNG, PDF, or standalone HTML (format = --to extension)"},args:{target:{type:"positional",required:!1,description:"Screen, frame, or board id. Omit to pick interactively."},design:{type:"string",description:DESIGN_ARG_DESCRIPTION},to:{type:"string",description:"Output path; extension picks the format (.png | .pdf | .html). Default: ./<target>.png"},mode:{type:"string",description:"light (default) | dark | compare (side-by-side light|dark; PNG, non-board only)"},scale:{type:"string",description:"PNG raster density (0.25\u20133; default 1; 2 = retina)"},theme:{type:"string",description:"Named theme to render with (default: the board's pin, else the folder default)"},w:{type:"string",description:"Viewport width for screen targets (default: first preset)"},h:{type:"string",description:"Viewport height for screen targets (default: first preset)"},yes:{type:"boolean",description:"Export even when a component fails to render (required without an interactive terminal)"}},async run({args}){let folder=await resolveDesign(args.design,"export",{designFlag:"--design"}),pipeline=await loadPipeline(folder),design=pipeline.design,mode=args.mode??"light";if(!["light","dark","compare"].includes(mode))fail("export",`--mode must be light, dark, or compare \u2014 got '${args.mode}'`);let scale=args.scale?Number(args.scale):1;if(!Number.isFinite(scale)||scale<0.25||scale>3)fail("export",`--scale must be between 0.25 and 3 \u2014 got '${args.scale}'`);let targetId=args.target;if(!targetId){let options=[...[...design.boards.values()].map((b)=>({value:b.id,label:b.name,hint:`board \xB7 ${b.frames.length} frame${b.frames.length===1?"":"s"}`})),...[...design.screens.values()].map((s)=>({value:s.id,label:s.name||s.id,hint:"screen"}))];if(options.length===0)fail("export",`nothing to export in ${folder}.`);if(!process.stdin.isTTY)fail("export",`pass a target: ${options.map((o)=>o.value).join(", ")} (boards and screens; frame ids work too)`);let chosen=await select({message:"Export what?",options});if(isCancel(chosen))fail("export","cancelled.");targetId=chosen}let screen=design.screens.get(targetId),framed=screen?null:findFrame(design,targetId),board=screen||framed?null:design.boards.get(targetId),kind=screen?"screen":framed?"frame":board?"board":null;if(!kind)fail("export",`no screen, frame, or board with id "${targetId}" in ${folder}. Boards: ${[...design.boards.keys()].join(", ")||"(none)"}; screens: ${[...design.screens.keys()].join(", ")||"(none)"}.`);let name=screen?.name||framed?.frame.label||(framed?design.screens.get(framed.frame.screen)?.name:void 0)||board?.name||targetId,outPath=args.to?isAbsolute(args.to)?args.to:resolve(args.to):resolve(exportFilename(name,"png")),ext=extname(outPath).toLowerCase().slice(1);if(ext!=="png"&&ext!=="pdf"&&ext!=="html")fail("export",`unsupported output extension ".${ext}" \u2014 use .png, .pdf, or .html.`);let format=ext;if(mode==="compare"&&(format!=="png"||kind==="board"))fail("export",'mode "compare" is PNG-only and not available for boards.');let preset=design.config.viewportPresets[0]??{name:"default",w:1440,h:900},viewport={w:args.w?Number(args.w):preset.w,h:args.h?Number(args.h):preset.h};await confirmRenderFailures("export",{folder:design,providers:pipeline.providers,defaultProvider:pipeline.defaultProvider},screensForExportTarget(design,kind,targetId),args.yes===!0);let opts={mode,scale,...args.theme?{theme:args.theme}:{}},assetOrigin,p={folder:design,providers:pipeline.providers,defaultProvider:pipeline.defaultProvider,snapshotCss:async()=>pipeline.snapshotCss,assetOrigin:()=>assetOrigin},produce=async()=>{if(kind==="screen"&&screen){if(format==="html"){let out=await exportScreenHtml(p,screen,{dark:mode==="dark",...args.theme?{theme:args.theme}:{},viewport});return{bytes:out.html,warnings:out.warnings}}if(format==="pdf")return{bytes:await exportScreenPdf(p,screen,{...opts,viewport}),warnings:[]};return{bytes:await exportScreenPng(p,screen,{...opts,viewport}),warnings:[]}}if(kind==="frame"&&framed){if(format==="html"){let out=await exportFrameHtml(p,framed.board,framed.frame,opts);return{bytes:out.html,warnings:out.warnings}}return{bytes:format==="pdf"?await exportFramePdf(p,framed.board,framed.frame,opts):await exportFramePng(p,framed.board,framed.frame,opts),warnings:[]}}if(!board)fail("export","internal: target resolution failed");if(format==="html"){let out=await exportBoardHtml(p,board,opts);return{bytes:out.html,warnings:out.warnings}}return{bytes:format==="pdf"?await exportBoardPdf(p,board,opts):await exportBoardPng(p,board,opts),warnings:[]}},result;try{if(format==="html")result=await produce();else await withAssetServer(folder,null,async(baseHref)=>{assetOrigin=baseHref,await captureWithBrowserSetup("export",async()=>{result=await produce()})},{host:hostFilesFetch(()=>folder)})}finally{await closePooledBrowser()}if(!result)fail("export","capture produced no output");if(typeof result.bytes==="string")await writeText(outPath,result.bytes);else await Bun.write(outPath,result.bytes);console.log(`velloo export: wrote ${outPath} (${kind}=${targetId}, mode=${mode})`);for(let warning of result.warnings)console.log(` note: ${warning}`)}});export{export_default as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{PACKAGE_VERSION,TOOL_VERSION,BUILD_TIME_MS}from"./chunk-
|
|
2
|
+
import{PACKAGE_VERSION,TOOL_VERSION,BUILD_TIME_MS}from"./chunk-42bj6r30.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM}from"./chunk-2smsg2ct.js";import{existsSync,realpathSync}from"fs";import{readFile}from"fs/promises";import{homedir}from"os";import{dirname,join}from"path";var DEFAULT_DOWNLOAD_BASE={stable:"https://get.velloo.design",dev:"https://get.dev.velloo.design"},NPM_LATEST_URL="https://registry.npmjs.org/velloo/latest";function localReleasePath(){return process.env.VELLOO_LOCAL_RELEASE??join(homedir(),".velloo","local-release.json")}async function readLocalRelease(){try{let raw=JSON.parse(await readFile(localReleasePath(),"utf8"));if(typeof raw!=="object"||raw===null)return null;let value=raw;if(typeof value.builtAt!=="number"||typeof value.tarball!=="string")return null;return{version:typeof value.version==="string"?value.version:PACKAGE_VERSION,build:typeof value.build==="string"?value.build:value.version??PACKAGE_VERSION,builtAt:value.builtAt,tarball:value.tarball,repo:typeof value.repo==="string"?value.repo:dirname(value.tarball)}}catch{return null}}function looksLikeHomebrew(env){let entry=env.VELLOO_LAUNCHER_PATH??Bun.main;if(!entry)return!1;try{return/[/\\]Cellar[/\\]velloo[/\\]/.test(realpathSync(entry))}catch{return/[/\\]Cellar[/\\]velloo[/\\]/.test(entry)}}var EXPLICIT=["npm","direct","homebrew","local","source"];function installMethod(env=process.env){let declared=env.VELLOO_INSTALL_METHOD;if(declared&&EXPLICIT.includes(declared)){if(declared==="npm"&&looksLikeHomebrew(env))return"homebrew";return declared}if(looksLikeHomebrew(env))return"homebrew";return"unknown"}function selfUpgradable(method){return method==="npm"||method==="direct"||method==="homebrew"||method==="local"}function releaseChannel(env=process.env){let declared=env.VELLOO_RELEASE_CHANNEL??bakedChannel();return declared==="dev"||declared==="local"?declared:"stable"}function bakedChannel(){return"stable"}function downloadBase(env=process.env){let explicit=env.VELLOO_DOWNLOAD_BASE;if(explicit)return explicit.replace(/\/+$/,"");let channel=releaseChannel(env);return DEFAULT_DOWNLOAD_BASE[channel==="local"?"stable":channel]}function installerUrl(env=process.env){return env.VELLOO_INSTALLER_URL??`${downloadBase(env)}/install.sh`}function latestVersionUrl(env=process.env){if(env.VELLOO_UPDATE_URL)return env.VELLOO_UPDATE_URL;if(releaseChannel(env)==="stable"&&installMethod(env)==="npm")return NPM_LATEST_URL;return`${downloadBase(env)}/latest.json`}function runningRelease(){return{version:PACKAGE_VERSION,build:TOOL_VERSION,...BUILD_TIME_MS===void 0?{}:{builtAt:BUILD_TIME_MS}}}function compareVersions(left,right){let parse=(value)=>{let match=/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);if(!match)return null;return{core:[Number(match[1]),Number(match[2]),Number(match[3])],prerelease:match[4]??null}},a=parse(left),b=parse(right);if(!a||!b)return 0;for(let index=0;index<3;index++){let delta=(a.core[index]??0)-(b.core[index]??0);if(delta!==0)return Math.sign(delta)}if(a.prerelease===b.prerelease)return 0;if(a.prerelease===null)return 1;if(b.prerelease===null)return-1;return a.prerelease.localeCompare(b.prerelease,void 0,{numeric:!0})}function isNewerRelease(candidate,running=runningRelease()){let byVersion=compareVersions(candidate.version,running.version);if(byVersion!==0)return byVersion>0;if(typeof candidate.builtAt!=="number")return!1;if(typeof running.builtAt!=="number")return!0;return candidate.builtAt>running.builtAt}function releaseLabel(info){return info.build??info.version}var FETCH_TIMEOUT_MS=2000;async function fetchLatestRelease(env=process.env){if(releaseChannel(env)==="local"&&!env.VELLOO_UPDATE_URL){let local=await readLocalRelease();if(!local)return null;return{version:local.version,build:local.build,builtAt:local.builtAt}}let response=await fetch(latestVersionUrl(env),{headers:{accept:"application/json","user-agent":`velloo/${PACKAGE_VERSION}`},signal:AbortSignal.timeout(FETCH_TIMEOUT_MS)});if(!response.ok)throw Error(`release check returned HTTP ${response.status}`);let body=await response.json();if(typeof body.version!=="string"||!/^\d+\.\d+\.\d+/.test(body.version))throw Error("release check returned an invalid version");return{version:body.version,...typeof body.build==="string"?{build:body.build}:{},...typeof body.builtAt==="number"?{builtAt:body.builtAt}:{}}}function installedVellooBin(env=process.env){let candidates=[],method=installMethod(env);if(method==="direct")candidates.push(join(env.VELLOO_BIN_DIR??join(homedir(),".local","bin"),"velloo"));if(method==="homebrew"){let prefix=env.HOMEBREW_PREFIX??(process.platform==="darwin"?"/opt/homebrew":null);if(prefix)candidates.push(join(prefix,"bin","velloo"));candidates.push("/usr/local/bin/velloo")}if(method==="npm"||method==="local"){let node=env.VELLOO_NODE_EXECUTABLE;if(node)candidates.push(join(dirname(node),"velloo"));let prefix=npmGlobalPrefix(env);if(prefix)candidates.push(process.platform==="win32"?join(prefix,"velloo.cmd"):join(prefix,"bin","velloo"))}return candidates.find((path)=>existsSync(path))??null}function npmGlobalPrefix(env){try{let result=Bun.spawnSync([npmExecutable(env),"prefix","-g"],{env}),out=result.stdout.toString().trim();return result.success&&out?out:null}catch{return null}}function npmExecutable(env=process.env){let node=env.VELLOO_NODE_EXECUTABLE;if(node){let adjacent=join(dirname(node),process.platform==="win32"?"npm.cmd":"npm");if(existsSync(adjacent))return adjacent}return process.platform==="win32"?"npm.cmd":"npm"}var import_picocolors=__toESM(require_picocolors(),1);import{existsSync as existsSync2}from"fs";import{mkdir,mkdtemp,readFile as readFile2,rename,rm,writeFile}from"fs/promises";import{homedir as homedir2,tmpdir}from"os";import{dirname as dirname2,join as join2}from"path";var CHECK_INTERVAL_MS=86400000,LOCAL_CHECK_INTERVAL_MS=1e4;function releaseSource(){return releaseChannel()==="local"&&!process.env.VELLOO_UPDATE_URL?`file:${localReleasePath()}`:latestVersionUrl()}function cachePath(){return process.env.VELLOO_UPDATE_CACHE??join2(homedir2(),".velloo","update-check.json")}async function readCacheFile(){try{let value=JSON.parse(await readFile2(cachePath(),"utf8"));if(typeof value!=="object"||value===null)return{};return Object.fromEntries(Object.entries(value).filter(([,entry])=>typeof entry==="object"&&entry!==null))}catch{return{}}}async function readCache(){let entry=(await readCacheFile())[releaseSource()];if(typeof entry?.checkedAt!=="number"||typeof entry.latest!=="string")return null;return entry}async function writeCache(value){let path=cachePath(),file={...await readCacheFile(),[releaseSource()]:value};await mkdir(dirname2(path),{recursive:!0});let temporary=`${path}.${process.pid}.tmp`;await writeFile(temporary,`${JSON.stringify(file,null,2)}
|
|
3
3
|
`);try{await rename(temporary,path)}catch(error){if(process.platform!=="win32")throw error;await rm(path,{force:!0}),await rename(temporary,path)}}function cachedRelease(cache){return{version:cache.latest,...cache.latestBuild===void 0?{}:{build:cache.latestBuild},...cache.latestBuiltAt===void 0?{}:{builtAt:cache.latestBuiltAt}}}async function refreshUpdateCache(){try{let previous=await readCache(),latest=await fetchLatestRelease();if(!latest)return;await writeCache({checkedAt:Date.now(),latest:latest.version,...latest.build===void 0?{}:{latestBuild:latest.build},...latest.builtAt===void 0?{}:{latestBuiltAt:latest.builtAt},...previous?.notifiedAt?{notifiedAt:previous.notifiedAt}:{},...previous?.notifiedVersion?{notifiedVersion:previous.notifiedVersion}:{}})}catch{}}function spawnUpdateCheck(){let entry=Bun.main,argv=existsSync2(entry)?[process.execPath,entry,"__update_check"]:[process.execPath,"__update_check"];try{Bun.spawn(argv,{stdin:"ignore",stdout:"ignore",stderr:"ignore",env:{...process.env,VELLOO_UPDATE_CHECK_CHILD:"1"}}).unref()}catch{}}function checkIntervalMs(){return releaseChannel()==="local"?LOCAL_CHECK_INTERVAL_MS:CHECK_INTERVAL_MS}function checkSuppressed(){return process.env.VELLOO_DISABLE_UPDATE_CHECK==="1"||process.env.VELLOO_UPDATE_CHECK_CHILD==="1"||!selfUpgradable(installMethod())}async function maybeNotifyAboutUpdate(options={}){if(checkSuppressed()||!(options.isTTY??process.stderr.isTTY))return;let now=options.now??Date.now(),cached=await readCache(),latest=(releaseChannel()==="local"?await fetchLatestRelease().catch(()=>null):null)??(cached?cachedRelease(cached):null);if(latest&&isNewerRelease(latest)){if(!(cached?.notifiedVersion===releaseLabel(latest)&&typeof cached?.notifiedAt==="number"&&now-cached.notifiedAt<checkIntervalMs())){let running=runningRelease();console.error(""),console.error(`${import_picocolors.default.yellow("Update available:")} ${releaseLabel(running)} \u2192 ${releaseLabel(latest)}. Run ${import_picocolors.default.cyan("velloo upgrade")}.`),await writeCache({checkedAt:cached?.checkedAt??now,latest:latest.version,...latest.build===void 0?{}:{latestBuild:latest.build},...latest.builtAt===void 0?{}:{latestBuiltAt:latest.builtAt},notifiedAt:now,notifiedVersion:releaseLabel(latest)}).catch(()=>{})}}if(!cached||now-cached.checkedAt>=checkIntervalMs())spawnUpdateCheck()}async function runInherited(command,env=process.env){let code=await Bun.spawn(command,{stdin:"inherit",stdout:"inherit",stderr:"inherit",env}).exited;if(code!==0)throw Error(`\`${command.join(" ")}\` exited with status ${code}`)}async function updateStatus(opts={}){let method=installMethod(),channel=releaseChannel(),running=runningRelease(),base={method,channel,current:releaseLabel(running),latest:null,available:!1,upgradable:selfUpgradable(method),...selfUpgradable(method)?{}:{reason:unsupportedReason(method)}};if(!selfUpgradable(method))return base;let release=null;if(opts.refresh)try{if(release=await fetchLatestRelease(),release)await writeCache({checkedAt:Date.now(),latest:release.version,...release.build===void 0?{}:{latestBuild:release.build},...release.builtAt===void 0?{}:{latestBuiltAt:release.builtAt}}).catch(()=>{})}catch(error){return{...base,reason:error instanceof Error?error.message:String(error)}}else{let cached=await readCache();if(cached)release=cachedRelease(cached);if(!cached||Date.now()-cached.checkedAt>=checkIntervalMs())spawnUpdateCheck()}if(!release)return base;return{...base,latest:releaseLabel(release),available:isNewerRelease(release,running)}}function unsupportedReason(method){return method==="source"?"this Velloo is running from source; update the checkout with your Git workflow":"could not identify this installation. Reinstall with `npm install -g velloo@latest` or the installer at https://get.velloo.design/install.sh"}async function upgradeInstalledVelloo(options={}){let method=installMethod(),channel=releaseChannel(),running=runningRelease();if(!selfUpgradable(method))throw Error(unsupportedReason(method));let latest=await fetchLatestRelease();if(!latest)throw Error(channel==="local"?`no local build found at ${localReleasePath()} \u2014 run \`bun run cli:build\` in the velloo checkout first`:"this channel has no published release");let done=(upgraded)=>({upgraded,from:releaseLabel(running),to:releaseLabel(latest),method,channel});if(!isNewerRelease(latest,running))return console.log(`velloo: ${releaseLabel(running)} is already the latest ${channel} build.`),done(!1);if(options.checkOnly)return console.log(`velloo: ${releaseLabel(running)} \u2192 ${releaseLabel(latest)} is available (${method} installation, ${channel} channel).`),done(!1);if(method==="homebrew")await runInherited(["brew","upgrade",process.env.VELLOO_BREW_FORMULA??"velloo"]);else if(channel==="local")await upgradeFromLocalBuild();else if(method==="npm"){let packageName=process.env.VELLOO_NPM_PACKAGE??"velloo",spec=channel==="dev"?`${downloadBase()}/velloo.tgz`:`${packageName}@${latest.version}`;await runInherited([npmExecutable(),"install","-g",spec])}else{let response=await fetch(installerUrl(),{signal:AbortSignal.timeout(1e4)});if(!response.ok)throw Error(`installer download returned HTTP ${response.status}`);let directory=await mkdtemp(join2(tmpdir(),"velloo-upgrade-")),installer=join2(directory,"install.sh");try{await writeFile(installer,await response.text(),{mode:448}),await runInherited(["bash",installer],{...process.env,VELLOO_VERSION:latest.version,VELLOO_DOWNLOAD_BASE:downloadBase()})}finally{await rm(directory,{recursive:!0,force:!0})}}return console.log(`velloo: upgraded ${releaseLabel(running)} \u2192 ${releaseLabel(latest)} via ${method}.`),done(!0)}async function upgradeFromLocalBuild(){let local=await readLocalRelease();if(!local)throw Error("no local build recorded \u2014 run `bun run cli:build` first");if(!existsSync2(local.tarball))throw Error(`the recorded local build is gone (${local.tarball}) \u2014 run \`bun run cli:build\` in ${local.repo} again`);await runInherited([npmExecutable(),"install","-g","--include=optional",local.tarball])}
|
|
4
4
|
export{installedVellooBin,refreshUpdateCache,maybeNotifyAboutUpdate,updateStatus,upgradeInstalledVelloo};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{detectTailwindMajor,tsconfigAliases,findDesignSystemIn}from"./chunk-66qgm91k.js";import{isServerRenderedApp,hasHtmxMarkup}from"./chunk-nbbnb5e9.js";import{existsSync,readFileSync}from"fs";import{join,posix}from"path";function readJson(path){try{return JSON.parse(readFileSync(path,"utf8"))}catch{return null}}function depRange(deps,name){let v=deps[name];return typeof v==="string"?v:void 0}var REACT_DEPS=["react","react-dom","next","preact","@remix-run/react","react-router"],SCRIPT_UI_DEPS=[...REACT_DEPS,"vue","nuxt","svelte","@sveltejs/kit","@angular/core","solid-js","astro","lit","@builder.io/qwik"];function isStaticSite(appRoot,deps){return existsSync(join(appRoot,"index.html"))&&!SCRIPT_UI_DEPS.some((name)=>depRange(deps,name))}function detectHost(appRoot,repoRoot=appRoot){let pkg=readJson(join(appRoot,"package.json"))??{},deps={...pkg.dependencies??{},...pkg.devDependencies??{}},tailwindMajor=detectTailwindMajor(appRoot),componentsJson=readJson(join(appRoot,"components.json")),shadcn=componentsJson!==null,shadcnStyle=typeof componentsJson?.style==="string"?componentsJson.style:void 0,react=REACT_DEPS.some((name)=>depRange(deps,name)),uiLibrary=depRange(deps,"@mui/material")?"mui":depRange(deps,"antd")?"antd":depRange(deps,"@chakra-ui/react")?"chakra":shadcn?"shadcn":!react&&(depRange(deps,"htmx.org")||depRange(deps,"htmx")||isServerRenderedApp(appRoot)||hasHtmxMarkup(appRoot)||isStaticSite(appRoot,deps))?"html":void 0,unsupportedUi=uiLibrary?void 0:detectUnsupportedUi(deps),designMdPath=findDesignSystemIn([appRoot,repoRoot])??void 0;return{shadcn,shadcnStyle,tailwindMajor,globalsCssPath:findGlobalsCss(appRoot,componentsJson),...designMdPath?{designMdPath}:{},...uiLibrary?{uiLibrary}:{},...unsupportedUi?{unsupportedUi}:{}}}var COMPONENT_DIR_CANDIDATES=["src/components/ui","components/ui","app/components/ui","src/app/components/ui","src/components","components"];function findComponentsDir(appRoot){return componentsJsonUiDir(appRoot)??COMPONENT_DIR_CANDIDATES.find((rel)=>existsSync(join(appRoot,rel)))}function componentsJsonUiDir(appRoot){let aliases=readJson(join(appRoot,"components.json"))?.aliases,ui=typeof aliases?.ui==="string"?aliases.ui:typeof aliases?.components==="string"?`${aliases.components}/ui`:void 0;if(ui===void 0)return;for(let{from,to}of tsconfigAliases(appRoot)){if(!from||!ui.startsWith(from))continue;let rel=posix.normalize(`${to}${ui.slice(from.length)}`).replace(/\/$/,"");if(existsSync(join(appRoot,rel)))return rel}return}function detectUnsupportedUi(deps){let known=[["@mantine/core","Mantine"],["@nextui-org/react","NextUI"],["@heroui/react","HeroUI"],["react-bootstrap","React Bootstrap"],["@fluentui/react-components","Fluent UI"],["@untitledui/icons","Untitled UI"],["@untitledui/file-icons","Untitled UI"],["react-aria-components","React Aria"]];for(let[pkg,label]of known)if(depRange(deps,pkg))return label;return}function findGlobalsCss(appRoot,componentsJson){let candidates=[componentsJson&&typeof componentsJson.tailwind?.css==="string"?componentsJson.tailwind.css:void 0,"src/app/globals.css","app/globals.css","src/styles/globals.css","styles/globals.css","src/index.css","src/globals.css","src/app.css"].filter((c)=>Boolean(c));for(let rel of candidates){let abs=join(appRoot,rel);if(existsSync(abs))return abs}return}
|
|
3
|
+
export{detectHost,findComponentsDir};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{isCancel,confirm,select}from"./chunk-80fjfyha.js";import{designNameIssue}from"./chunk-
|
|
2
|
+
import{isCancel,confirm,select}from"./chunk-80fjfyha.js";import{designNameIssue}from"./chunk-4pkw6d9n.js";import{managedDesignId,localDesignOf,removeLocalDesign,recordedDesignName,designRenameConflict,followDesignRename,loadDesignFolder}from"./chunk-66qgm91k.js";import"./chunk-ngbqd7kb.js";import"./chunk-afe25dpr.js";import{agentRootCandidates}from"./chunk-33t0csxs.js";import"./chunk-42bj6r30.js";import{daemonRoot,readLock,isLive,stopDaemon}from"./chunk-asvdqkdp.js";import{fail}from"./chunk-s23gtejf.js";import"./chunk-5ma8zbty.js";import"./chunk-cqt1dpxd.js";import"./chunk-sb5h27w9.js";import{displayPath}from"./chunk-vryadew3.js";import"./chunk-0wqxb8r9.js";import"./chunk-0wafc9aw.js";import{recordedAppRoot,planAppRootChange,applyAppRootChange,promptAppRootChoice,planRelocation,relocateDesign,runInit2}from"./chunk-tw4kw4ps.js";import"./chunk-wkqrb2tz.js";import{repinAgentConfigs,migrateFolder2}from"./chunk-a16mz9zf.js";import"./chunk-pjtg3wnm.js";import"./chunk-b3x80jc1.js";import"./chunk-4sjvdakg.js";import{findManifest,manifestListing,findDesigns,unregisterDesign,isWithin,writeDesignName,registerLocalDesign,DESIGN_ARG_DESCRIPTION,hasDesignConfig,DESIGN_ID_ARG,resolveDesign,checkoutDesigns}from"./chunk-675tr675.js";import"./chunk-edgwx026.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM,defineCommand}from"./chunk-2smsg2ct.js";import{randomUUID}from"crypto";import{existsSync,realpathSync}from"fs";import{readdir,rm}from"fs/promises";import{join,relative,resolve}from"path";var import_picocolors=__toESM(require_picocolors(),1);async function countJson(dir,skipSuffix){try{return(await readdir(dir)).filter((f)=>f.endsWith(".json")&&!(skipSuffix&&f.endsWith(skipSuffix))).length}catch{return 0}}async function daemonState(folder){let rec=await readLock(daemonRoot(folder));if(!rec)return"stopped";return await isLive(rec)?`running :${rec.canvasPort}`:"stopped"}var list=defineCommand({meta:{name:"list",description:"List the repo's designs"},async run(){let cwd=resolve("."),found=await findDesigns(cwd).catch((err)=>{fail("design list",err.message)});if(!found||found.designs.length===0){let only=await resolveDesign(void 0,"design list",{requireConfig:!0,cwd,takesDesign:!1,onFail:(message)=>fail("design list",message)}),name=recordedDesignName(only)??"(unnamed)",owner=await manifestListing(only).catch(()=>null);if(owner&&owner.dir!==cwd){console.log(`velloo design: "${name}" (${relative(cwd,only)||only}) is listed in \`${relative(cwd,owner.path)}\`. Run \`velloo design list\` from \`${relative(cwd,owner.dir)||"."}\` to see that project's designs.`);return}console.log("velloo design: 1 design (no velloo.json yet)"),console.log(` ${import_picocolors.default.dim("1")} ${import_picocolors.default.bold(name)} ${relative(cwd,only)||only} ${await daemonState(only)}`),console.log(import_picocolors.default.dim(" `velloo design add` creates a second design and lists both."));return}let base=found.repo?.dir??found.local[0]?.root??cwd,rows=[];for(let design of found.designs){let live=design.exists&&!design.outsideRepo;rows.push({name:found.conflicts.has(design.name)?`${design.name} (duplicate)`:design.name,path:design.local?`${displayPath(design.root)} (local)`:relative(base,design.root)||".",boards:live?await countJson(join(design.root,"boards"),".notes.json"):0,state:design.outsideRepo?import_picocolors.default.yellow("outside the repo"):live?await daemonState(design.root):import_picocolors.default.yellow("missing"),live})}let pad=(text,width)=>text+" ".repeat(Math.max(0,width-Bun.stringWidth(text))),width=Math.max(...rows.map((r)=>Bun.stringWidth(r.name))),pathWidth=Math.max(...rows.map((r)=>Bun.stringWidth(r.path))),idWidth=String(rows.length).length;console.log(`velloo design: ${rows.length} in ${base}`);for(let[index,row]of rows.entries()){let marker=row.name===found.defaultDesign?import_picocolors.default.cyan("\u25CF"):" ",id=import_picocolors.default.dim(String(index+1).padStart(idWidth)),boards=row.live?`${row.boards} board${row.boards===1?"":"s"}`:"\u2014";console.log(` ${id} ${marker} ${import_picocolors.default.bold(pad(row.name,width))} ${import_picocolors.default.dim(pad(row.path,pathWidth))} ${boards.padEnd(9)} ${row.state}`)}console.log(import_picocolors.default.dim(" Pass a number as --id to the other `velloo design` commands."));for(let[name,designs]of found.conflicts)console.log(import_picocolors.default.yellow(` ! ${designs.length} designs are named "${name}" \u2014 rename one with \`velloo design rename <path> <new-name>\`.`))}}),add=defineCommand({meta:{name:"add",description:"Create another design in this repo (the init wizard, folder pre-chosen)"},args:{path:{type:"positional",required:!1,description:"Where the new design folder goes, relative to the repo (asked when omitted)"},external:{type:"boolean",description:"Create in Velloo-managed external storage"},nonInteractive:{type:"boolean",description:"Use defaults without prompting"},connect:{type:"boolean",default:!0,description:"Wire project agents"},library:{type:"string",description:"Component library (default: the sibling folder's)"},name:{type:"string",description:"The design's name (default: derived from its folder)"}},async run({args}){await runInit2({addFolder:!0,external:args.external,nonInteractive:args.nonInteractive,connect:args.connect,...args.path?{designFolder:args.path}:{},...args.library?{library:args.library}:{},...args.name?{name:args.name}:{}})}}),remove=defineCommand({meta:{name:"remove",description:"Remove a design \u2014 one outside the repo can keep its files (asked, or --delete-content)"},args:{design:{type:"positional",required:!1,description:DESIGN_ARG_DESCRIPTION},id:DESIGN_ID_ARG,deleteContent:{type:"boolean",description:"Also delete the files of a design outside the repo (with --yes, or to skip the question)"},yes:{type:"boolean",description:"Skip the confirmation (required without an interactive terminal)"}},async run({args}){let cwd=resolve("."),folder=await resolveDesign(args.design,"design remove",{id:args.id,interactive:!0,requireConfig:!0,cwd}),local=localDesignOf(folder);if(!Boolean(process.stdin.isTTY)&&args.yes!==!0)fail("design remove","refusing to delete a design folder without --yes");let boards=await countJson(join(folder,"boards"),".notes.json"),screens=await countJson(join(folder,"screens"),".annotations.json"),name=recordedDesignName(folder)??folder;console.log(`velloo design: ${name} (${folder})`),console.log(import_picocolors.default.dim(` ${boards} board${boards===1?"":"s"}, ${screens} screen${screens===1?"":"s"}`));let keepContent=local!==null&&args.deleteContent!==!0;if(args.yes!==!0)if(local&&args.deleteContent!==!0){let managed=managedDesignId(folder)!==null,choice=await select({message:"Remove this design?",initialValue:managed?"delete":"keep",options:[{value:"delete",label:"Delete it and its files",hint:managed?"in Velloo's storage":folder},{value:"keep",label:"Forget it, keep the files",hint:folder},{value:"cancel",label:"Cancel"}]});if(isCancel(choice)||choice==="cancel"){console.log(import_picocolors.default.dim(" Nothing changed."));return}keepContent=choice==="keep"}else{let approved=await confirm({message:"Delete this design folder and everything in it?",initialValue:!1});if(isCancel(approved)||!approved){console.log(import_picocolors.default.dim(" Nothing changed."));return}}if(await stopDaemon(daemonRoot(folder)))console.log(import_picocolors.default.dim(" Stopped its canvas."));let dropped=local?null:await unregisterDesign(folder,cwd).catch((err)=>fail("design remove",err.message));if(local)await removeLocalDesign(local.id);if(!keepContent)await rm(folder,{recursive:!0,force:!0});if(console.log(`velloo design: ${keepContent?"retained content at":"removed"} ${folder}`),local)console.log(import_picocolors.default.dim(` Forgot the local design "${name}".`));if(dropped?.name)console.log(import_picocolors.default.dim(dropped.removedManifest?` Dropped "${dropped.name}" \u2014 velloo.json listed no other designs, so it's gone too.`:` Dropped "${dropped.name}" from velloo.json.`))}}),move=defineCommand({meta:{name:"move",description:"Preview moving a design between the repository, managed storage and another directory; --yes applies"},args:{design:{type:"positional",required:!1,description:DESIGN_ARG_DESCRIPTION},id:DESIGN_ID_ARG,external:{type:"boolean",description:"Move to managed storage (a local design)"},to:{type:"string",description:"New directory \u2014 inside the repository it goes in velloo.json, outside it is local"},yes:{type:"boolean",description:"Apply the displayed move"}},async run({args}){let source=await resolveDesign(args.design,"design move",{id:args.id,interactive:!0,requireConfig:!0}),plan=await planRelocation(source,resolve("."),args.to,args.external===!0).catch((err)=>fail("design move",err.message)),local="a local design on this machine",recorded=plan.sourceManifestPath?plan.destinationLocal?`velloo.json path "${plan.sourceManifestPath}" \u2192 ${"a local design on this machine"}`:`velloo.json path "${plan.sourceManifestPath}" \u2192 "${plan.destinationManifestPath}"`:plan.destinationLocal?"stays a local design on this machine":`${"a local design on this machine"} \u2192 velloo.json path "${plan.destinationManifestPath}"`;if(console.log(`Source: ${plan.source}
|
|
3
3
|
Destination: ${plan.destination}
|
|
4
4
|
Design "${plan.designName}": ${recorded}`),!args.yes){console.log("Preview only. Add --yes to apply the move.");return}await relocateDesign(plan),console.log(`Moved ${plan.designName} to ${plan.destination}. Run velloo connect to refresh existing agent guidance.`)}});async function renameThroughCanvas(folder,name){let rec=await readLock(daemonRoot(folder));if(!rec||!await isLive(rec))return!1;let res;try{res=await fetch(`http://127.0.0.1:${rec.canvasPort}/api/mutate/update_design_name`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name}),signal:AbortSignal.timeout(1e4)})}catch(err){fail("design rename",`the running canvas didn't answer (${err.message}).`)}if(res.ok)return!0;let body=await res.json().catch(()=>null);fail("design rename",body?.error?.message??`the running canvas refused (HTTP ${res.status}).`)}var rename=defineCommand({meta:{name:"rename",description:"Rename a design"},args:{design:{type:"positional",required:!1,description:"The design to rename \u2014 its current name or its folder path (or use --id)"},name:{type:"positional",required:!1,description:"The new name"},id:DESIGN_ID_ARG},async run({args}){let cwd=resolve("."),newName=args.id!==void 0&&args.name===void 0?args.design:args.name,target=args.id!==void 0&&args.name===void 0?void 0:args.design;if(newName===void 0)fail("design rename","pass the design and its new name: `velloo design rename <design> <new name>`.");let folder=await resolveDesign(target,"design rename",{id:args.id,requireConfig:!0,cwd}),previous=recordedDesignName(folder);if(previous===newName){console.log(import_picocolors.default.dim(` Already named "${newName}" \u2014 nothing changed.`));return}let conflict=await designRenameConflict(folder,newName).catch((err)=>fail("design rename",err.message));if(conflict)fail("design rename",conflict);if(!await renameThroughCanvas(folder,newName)){if(await writeDesignName(folder,newName),previous&&await followDesignRename(folder,previous,newName))console.log(import_picocolors.default.dim(` velloo.json defaultDesign \u2192 "${newName}"`))}console.log(`velloo design: renamed ${previous?`"${previous}" `:""}to "${newName}".`);let repinned=previous?await repinAgentConfigs({projectRoots:await agentRootCandidates(folder),designFolder:folder,names:[previous]}).catch(()=>[]):[];for(let path of repinned)console.log(import_picocolors.default.dim(` ${relative(cwd,path)||path}: no longer names the design`))}}),upgrade=defineCommand({meta:{name:"upgrade",description:"Migrate a design to this Velloo's format, without upgrading Velloo itself"},args:{design:{type:"positional",required:!1,description:DESIGN_ARG_DESCRIPTION},id:DESIGN_ID_ARG,all:{type:"boolean",description:"Migrate every design in this checkout"},"dry-run":{type:"boolean",default:!1,description:"Show what would change without writing"},skills:{type:"boolean",default:!0,description:"Also refresh the installed agent skills / plugin / rules to this velloo's versions"}},async run({args}){if(args.all&&(args.design||args.id!==void 0))fail("design upgrade","pass a design or --all, not both.");let folders=args.all?await checkoutDesigns(resolve(".")):[await resolveDesign(args.design,"design upgrade",{id:args.id,interactive:!0,requireConfig:!0})];if(folders.length===0)fail("design upgrade","no designs here \u2014 `velloo init` creates one.");for(let folder of folders)await migrateFolder2(folder,{dryRun:args["dry-run"],skills:args.skills})}}),bind=defineCommand({meta:{name:"bind",description:"Attach a design folder outside this checkout to it, on this machine only"},args:{design:{type:"positional",required:!0,description:"The design folder (in managed storage or anywhere outside the checkout)"},name:{type:"string",description:"Rename the design as it is bound"},yes:{type:"boolean",description:"Confirm the local binding"}},async run({args}){let cwd=resolve("."),folder=resolve(args.design);if(!await hasDesignConfig(folder))fail("design bind",`${folder} is not a velloo design folder (no .design/config.json).`);let root=cwd;if(isWithin(root,folder))fail("design bind",`${folder} is inside this checkout (${root}); a design here belongs in velloo.json \u2014 see \`velloo design add\`.`);let name=args.name??recordedDesignName(folder);if(!name)fail("design bind",`${folder} has no design name \u2014 run \`velloo upgrade ${folder}\` first.`);let issue=designNameIssue(name);if(issue)fail("design bind",`invalid --name ${JSON.stringify(name)} \u2014 ${issue}`);let clash=(await findDesigns(root).catch((err)=>fail("design bind",err.message)))?.designs.find((d)=>d.name===name&&!(existsSync(d.root)&&realpathSync(d.root)===realpathSync(folder)));if(clash)fail("design bind",`a design named "${name}" already exists at ${clash.root} \u2014 pass --name to bind this one under another.`);let existing=localDesignOf(folder),appRoot=existing?join(root,relative(existing.root,existing.appRoot)):root,listed=(await findManifest(cwd).catch(()=>null))?.folders.some((path)=>existsSync(path)&&realpathSync(path)===realpathSync(folder));if(console.log(`Design: ${name} (${folder})
|
|
5
5
|
Checkout: ${root}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{init_default,runInit2}from"./chunk-tw4kw4ps.js";import"./chunk-0wqxb8r9.js";import"./chunk-wkqrb2tz.js";import"./chunk-33t0csxs.js";import"./chunk-b3x80jc1.js";import"./chunk-vryadew3.js";import"./chunk-4sjvdakg.js";import"./chunk-5ma8zbty.js";import"./chunk-asvdqkdp.js";import"./chunk-675tr675.js";import"./chunk-80fjfyha.js";import"./chunk-66qgm91k.js";import"./chunk-ngbqd7kb.js";import"./chunk-edgwx026.js";import"./chunk-afe25dpr.js";import"./chunk-0wafc9aw.js";import"./chunk-cqt1dpxd.js";import"./chunk-42bj6r30.js";import"./chunk-s23gtejf.js";import"./chunk-4pkw6d9n.js";import"./chunk-sb5h27w9.js";export{init_default as default,runInit2 as runInit};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{isCancel,select}from"./chunk-80fjfyha.js";import{closePooledBrowser,renderScreen,screenshot}from"./chunk-ngbqd7kb.js";import{ScreenSchema}from"./chunk-4pkw6d9n.js";import{writeText,registryForScreen,renderPassForScreen,hostStylesheetsForScreen,hostFilesFetch}from"./chunk-66qgm91k.js";import"./chunk-afe25dpr.js";import{fail}from"./chunk-s23gtejf.js";import{captureWithBrowserSetup}from"./chunk-5ma8zbty.js";import{findDesignConfig}from"./chunk-z78c1kf8.js";import{loadPipeline}from"./chunk-6cgvzwec.js";import{withAssetServer}from"./chunk-vgx47anc.js";import{createProgress}from"./chunk-pjtg3wnm.js";import{DESIGN_ARG_DESCRIPTION,resolveDesign,pickScreen}from"./chunk-675tr675.js";import{defineCommand}from"./chunk-2smsg2ct.js";import{readFile}from"fs/promises";import{extname,isAbsolute,resolve}from"path";var render_default=defineCommand({meta:{name:"render",description:"dev: render a screen headless to .html or .png"},args:{screen:{type:"positional",required:!1,description:"Screen id, or a path to a screen JSON. Omit to pick interactively."},design:{type:"string",description:DESIGN_ARG_DESCRIPTION},to:{type:"string",description:"Output path; extension picks the format (.html | .png). Default: ./<screen>.html"},w:{type:"string",description:"Viewport width in px (default: 1440)"},h:{type:"string",description:"Viewport height in px (default: 900)"}},async run({args}){let interactive=Boolean(process.stdin.isTTY),screenArg=args.screen,looksLikePath=!!screenArg&&(screenArg.includes("/")||screenArg.endsWith(".json")),folder,screenPath;if(looksLikePath&&screenArg)if(screenPath=resolve(screenArg),args.design)folder=await resolveDesign(args.design,"render",{designFlag:"--design"});else{let found=await findDesignConfig(screenPath);if(!found)fail("render",`${screenPath} is not inside a velloo design folder (no .design/config.json above it). Pass --design with a design name or a path.`);folder=found.folder}else folder=await resolveDesign(args.design,"render",{designFlag:"--design"}),screenPath=(await pickScreen(folder,screenArg,interactive,"render")).path;let screen=ScreenSchema.parse(JSON.parse(await readFile(screenPath,"utf8"))),viewport={w:args.w?Number(args.w):1440,h:args.h?Number(args.h):900},outPath;if(args.to)outPath=isAbsolute(args.to)?args.to:resolve(args.to);else{let ext="html";if(interactive){let fmt=await select({message:"Output format?",options:[{value:"html",label:"HTML",hint:"no browser needed"},{value:"png",label:"PNG",hint:"screenshot (needs a headless browser)"}]});if(isCancel(fmt))fail("render","cancelled.");ext=fmt}outPath=resolve(`${screen.id}.${ext}`)}let out=extname(outPath).toLowerCase();if(out!==".html"&&out!==".png")fail("render",`unsupported output extension ${JSON.stringify(out)}. Use .html or .png.`);let progress=createProgress();progress.start("preparing render");try{let pipeline=await loadPipeline(folder),{design,config,providers,defaultProvider,snapshotCss}=pipeline,theme=design.theme,registry=registryForScreen(screen,providers,defaultProvider,config.extensions??{},config.styling?.framework),hostStylesheets=hostStylesheetsForScreen(screen,providers,defaultProvider,config.hostApp),renderPass=renderPassForScreen(screen,providers,defaultProvider,theme),renderHtml=async(baseHref)=>{let{html}=await renderScreen(screen,theme,{viewport,snapshotCss,registry,snippets:design.snippets,renderPass,customCss:design.customCss,...baseHref?{baseHref,hostStylesheets}:{}});return html};if(out===".html"){progress.step("rendering HTML"),await writeText(outPath,await renderHtml()),progress.succeed("rendered HTML"),console.log(`velloo render: wrote ${outPath} (screen=${screen.id})`);return}progress.step("rendering screen");let host=hostFilesFetch(()=>folder);await withAssetServer(folder,null,async(baseHref)=>{let html=await renderHtml(baseHref);try{await captureWithBrowserSetup("render",async()=>{progress.step("capturing PNG");try{await screenshot({html,viewport,outPath})}catch(error){throw progress.fail("PNG capture failed"),error}})}finally{await closePooledBrowser()}},{host}),progress.succeed("rendered PNG"),console.log(`velloo render: wrote ${outPath} (screen=${screen.id})`)}catch(error){throw progress.fail("render failed"),error}}});export{render_default as default};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{CHROMIUM_INSTALL_ARGV,CHROMIUM_FULL_INSTALL_ARGV,CHROMIUM_DEPS_INSTALL_ARGV}from"./chunk-
|
|
2
|
+
import{CHROMIUM_INSTALL_ARGV,CHROMIUM_FULL_INSTALL_ARGV,CHROMIUM_DEPS_INSTALL_ARGV}from"./chunk-ngbqd7kb.js";import"./chunk-4pkw6d9n.js";import{fail}from"./chunk-s23gtejf.js";import{defineCommand}from"./chunk-2smsg2ct.js";async function run(argv){let code=await Bun.spawn([...argv],{stdin:"inherit",stdout:"inherit",stderr:"inherit"}).exited;if(code!==0)fail("browser install",`browser setup exited with status ${code}`)}var install=defineCommand({meta:{name:"install",description:"Install the browser used for screenshots"},args:{full:{type:"boolean",default:!1,description:"Install full Chromium instead of the smaller headless shell"},"with-deps":{type:"boolean",default:!1,description:"Also install required Linux system packages (may use sudo)"}},async run({args}){if(args["with-deps"]&&process.platform==="linux")await run(CHROMIUM_DEPS_INSTALL_ARGV);await run(args.full?CHROMIUM_FULL_INSTALL_ARGV:CHROMIUM_INSTALL_ARGV)}}),browser_default=defineCommand({meta:{name:"browser",description:"Manage the optional browser used for screenshots"},subCommands:{install}});export{browser_default as default};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{localDesignOf}from"./chunk-
|
|
2
|
+
import{localDesignOf}from"./chunk-66qgm91k.js";import{TOOL_VERSION}from"./chunk-42bj6r30.js";import{isCancel,multiselect,log,select}from"./chunk-80fjfyha.js";import{manifestListing,findDesigns}from"./chunk-675tr675.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM}from"./chunk-2smsg2ct.js";import{existsSync}from"fs";import{homedir}from"os";import{dirname,join}from"path";var claudeEntry=(conn)=>conn.transport==="http"?{type:"http",url:conn.url}:{command:conn.command,args:conn.args},cursorEntry=(conn)=>conn.transport==="http"?{url:conn.url}:{command:conn.command,args:conn.args},codexEntry=cursorEntry,continueEntry=(conn)=>conn.transport==="http"?{name:"velloo",type:"streamable-http",url:conn.url}:{name:"velloo",type:"stdio",command:conn.command,args:conn.args},droidEntry=(conn)=>conn.transport==="http"?{type:"http",url:conn.url}:{type:"stdio",command:conn.command,args:conn.args,disabled:!1},geminiEntry=(conn)=>conn.transport==="http"?{httpUrl:conn.url}:{command:conn.command,args:conn.args},opencodeEntry=(conn)=>conn.transport==="http"?{type:"remote",url:conn.url,enabled:!0}:{type:"local",command:[conn.command,...conn.args],enabled:!0},vscodeEntry=(conn)=>conn.transport==="http"?{type:"http",url:conn.url}:{type:"stdio",command:conn.command,args:conn.args};function claudeDesktopConfigPath(homeDir){if(process.platform==="win32")return join(homeDir,"AppData","Roaming","Claude","claude_desktop_config.json");if(process.platform==="darwin")return join(homeDir,"Library","Application Support","Claude","claude_desktop_config.json");return join(homeDir,".config","Claude","claude_desktop_config.json")}var AGENTS={"claude-code":{id:"claude-code",label:"Claude Code (project)",scope:"project",format:"json",family:"claude-code",path:(root)=>join(root,".mcp.json"),entry:claudeEntry},cursor:{id:"cursor",label:"Cursor (project)",scope:"project",format:"json",family:"cursor",path:(root)=>join(root,".cursor","mcp.json"),entry:cursorEntry},codex:{id:"codex",label:"Codex CLI (project)",scope:"project",format:"codex-toml",path:(root)=>join(root,".codex","config.toml"),entry:codexEntry},continue:{id:"continue",label:"Continue (project)",scope:"project",format:"continue-block",path:(root)=>join(root,".continue","mcpServers","velloo.yaml"),entry:continueEntry},"claude-code-global":{id:"claude-code-global",label:"Claude Code (global)",scope:"global",format:"json",family:"claude-code",path:(_root,home)=>join(home,".claude.json"),entry:claudeEntry},"cursor-global":{id:"cursor-global",label:"Cursor (global)",scope:"global",format:"json",family:"cursor",path:(_root,home)=>join(home,".cursor","mcp.json"),entry:cursorEntry},"codex-global":{id:"codex-global",label:"Codex CLI (global)",scope:"global",format:"codex-toml",path:(_root,home)=>join(home,".codex","config.toml"),entry:codexEntry},"continue-global":{id:"continue-global",label:"Continue (global)",scope:"global",format:"continue-config",path:(_root,home)=>join(home,".continue","config.yaml"),entry:continueEntry},"claude-desktop":{id:"claude-desktop",label:"Claude Desktop",scope:"global",format:"json",gui:!0,path:(_root,home)=>claudeDesktopConfigPath(home),entry:claudeEntry},opencode:{id:"opencode",label:"opencode (project)",scope:"project",format:"opencode-json",path:(root)=>join(root,"opencode.json"),entry:opencodeEntry},"opencode-global":{id:"opencode-global",label:"opencode (global)",scope:"global",format:"opencode-json",path:(_root,home)=>join(home,".config","opencode","opencode.json"),entry:opencodeEntry},"droid-global":{id:"droid-global",label:"Droid (global)",scope:"global",format:"json",path:(_root,home)=>join(home,".factory","mcp.json"),entry:droidEntry},"cline-global":{id:"cline-global",label:"Cline (global)",scope:"global",format:"json",path:(_root,home)=>join(home,".cline","data","settings","cline_mcp_settings.json"),entry:cursorEntry},gemini:{id:"gemini",label:"Gemini CLI (project)",scope:"project",format:"json",path:(root)=>join(root,".gemini","settings.json"),entry:geminiEntry},"gemini-global":{id:"gemini-global",label:"Gemini CLI (global)",scope:"global",format:"json",path:(_root,home)=>join(home,".gemini","settings.json"),entry:geminiEntry},"windsurf-global":{id:"windsurf-global",label:"Windsurf (global)",scope:"global",format:"json",path:(_root,home)=>join(home,".codeium","windsurf","mcp_config.json"),entry:cursorEntry},vscode:{id:"vscode",label:"VS Code / Copilot (project)",scope:"project",format:"vscode-json",path:(root)=>join(root,".vscode","mcp.json"),entry:vscodeEntry}},AGENT_IDS=Object.keys(AGENTS),PROJECT_AGENT_IDS=["claude-code","cursor"],GLOBAL_AGENT_IDS=["claude-code-global","cursor-global"];function detectInstalledAgents(homeDir=homedir()){let has=(rel)=>existsSync(join(homeDir,rel)),found=[];if(Bun.which("claude")||has(".claude.json")||has(".claude"))found.push("claude-code-global");if(Bun.which("cursor-agent")||Bun.which("cursor")||has(".cursor"))found.push("cursor-global");if(Bun.which("codex")||has(".codex"))found.push("codex-global");if(has(".continue"))found.push("continue-global");if(existsSync(dirname(claudeDesktopConfigPath(homeDir))))found.push("claude-desktop");if(Bun.which("opencode")||has(join(".config","opencode")))found.push("opencode-global");if(Bun.which("droid")||has(".factory"))found.push("droid-global");if(Bun.which("cline")||has(".cline"))found.push("cline-global");if(Bun.which("gemini")||has(join(".gemini","settings.json")))found.push("gemini-global");if(has(join(".codeium","windsurf")))found.push("windsurf-global");return found}import{existsSync as existsSync2,realpathSync}from"fs";import{access}from"fs/promises";import{dirname as dirname2,join as join2,parse,relative,resolve,sep}from"path";async function resolveProjectRoot(designFolder,override){if(override)return override;let local=localDesignOf(designFolder);if(local)return local.root;let registered=await registeringManifest(designFolder);if(registered)return registered.dir;return nearestPackageRoot(designFolder)}async function designArgumentFor(projectRoot,designFolder){let set=await findDesigns(projectRoot).catch(()=>null),target=realOr(designFolder);if(set?.designs.some((d)=>!d.outsideRepo&&realOr(d.root)===target))return null;return relative(projectRoot,designFolder).split(sep).join("/")||"."}function realOr(path){return existsSync2(path)?realpathSync(path):resolve(path)}async function agentRootCandidates(designFolder){if(localDesignOf(designFolder))return[];let roots=[await resolveProjectRoot(designFolder)];return roots.push(await nearestPackageRoot(designFolder)),[...new Set(roots)]}async function registeringManifest(designFolder){let abs=resolve(designFolder),found;try{found=await manifestListing(abs)}catch{return null}if(!found)return null;let real=existsSync2(abs)?realpathSync(abs):abs;return found.folders.some((folder)=>folder===abs||existsSync2(folder)&&realpathSync(folder)===real)?found:null}async function nearestPackageRoot(designFolder){let fallback=dirname2(designFolder),dir=fallback,fsRoot=parse(dir).root;for(let i=0;i<8;i++){try{return await access(join2(dir,"package.json")),dir}catch{}if(dir===fsRoot)break;dir=dirname2(dir)}return fallback}import{existsSync as existsSync5}from"fs";import{homedir as homedir3}from"os";import{join as join6}from"path";import{mkdir,writeFile}from"fs/promises";import{dirname as dirname3,join as join3}from"path";async function installCursorRules(projectRoot,designFolder){let pinned=await designArgumentFor(projectRoot,designFolder),arg=pinned?` ${pinned}`:"",body=`---
|
|
3
3
|
description: Drive UI design through the Velloo MCP server (a local design canvas backed by this project's shadcn components).
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -36,5 +36,5 @@ Velloo is a local, code-shaped design canvas exposed over MCP. ${pinned?`The des
|
|
|
36
36
|
`;return Bun.TOML.parse(content),content}function toYaml(value){let yaml=Bun.YAML.stringify(value,null,2).replace(/[ \t]+$/gm,"");return yaml.endsWith(`
|
|
37
37
|
`)?yaml:`${yaml}
|
|
38
38
|
`}function continueBlockFile(entry){let version=TOOL_VERSION.split(" ")[0]??"0.0.0";return toYaml({name:"velloo",version,schema:"v1",mcpServers:[entry]})}function mergeContinueConfig(existing,entry,path){let doc={name:"Local Assistant",version:"1.0.0",schema:"v1"};if(existing!==null&&existing.trim()!==""){let parsed;try{parsed=Bun.YAML.parse(existing)}catch(err){throw invalidConfig(path,"YAML",err)}if(parsed===null||typeof parsed!=="object"||Array.isArray(parsed))throw invalidConfig(path,"YAML",Error("expected a top-level mapping"));doc=parsed}let others=(Array.isArray(doc.mcpServers)?doc.mcpServers:[]).filter((s)=>!(s!==null&&typeof s==="object"&&s.name==="velloo"));return toYaml({...doc,mcpServers:[...others,entry]})}var MANUAL_AGENT_ID="manual";function manualSetupText(){return["velloo speaks MCP over stdio (recommended) or HTTP \u2014 any MCP-capable agent can connect.","","stdio \u2014 the agent spawns the server itself:",' command: "velloo" args: ["mcp"]',` (resolves the design folder from the agent's cwd; pin one with ["mcp", "path/to/velloo"])`,""," Most agents take the JSON convention:",' { "mcpServers": { "velloo": { "command": "velloo", "args": ["mcp"] } } }',"","HTTP \u2014 for agents that dial a URL: `velloo mcp --http <design>` prints the URL of the","running canvas's MCP server, then point the agent at it:",' { "mcpServers": { "velloo": { "type": "http", "url": "<the printed URL>" } } }'," The port changes whenever the canvas restarts, so prefer stdio where the agent allows it."].join(`
|
|
39
|
-
`)}function baseLabel(id){return(AGENTS[id]?.label??id).replace(/\s*\((?:project|global)\)$/,"")}function projectVariantOf(id){let agent=AGENTS[id];if(!agent)return;if(agent.scope==="project")return id;let candidate=id.replace(/-global$/,"");return candidate!==id&&AGENTS[candidate]?candidate:void 0}async function askAgentWiring(opts){let preWired=await globallyWiredAgents(),projectWired=opts?.projectRoot&&!opts.globalOnly?await projectWiredAgents(opts.projectRoot):[],detected=detectInstalledAgents(),fallback=detected.length===0,wanted=fallback?GLOBAL_AGENT_IDS:detected,missing=wanted.filter((id)=>!preWired.includes(id));if(projectWired.length>0)log.info(`Already wired for this project: ${projectWired.map(baseLabel).join(", ")} ${import_picocolors.default.dim("(re-wiring is a no-op merge)")}`);if(preWired.length>0){let labels=preWired.map(baseLabel).join(", ");if(opts?.skipWhenCovered&&missing.length===0)return log.success(`velloo is already wired globally \u2014 ${labels} ${import_picocolors.default.dim("(covers this project; skipping agent setup)")}`),{agents:[],manual:!1,preWired};log.info(`Already wired globally: ${labels} ${import_picocolors.default.dim("(covers this project)")}`)}log.info(fallback?`No coding agents detected on this machine \u2014 offering the defaults (${wanted.map(baseLabel).join(", ")}).`:`Coding agents detected: ${detected.map(baseLabel).join(", ")}`);let targets=missing.length>0?missing:wanted,names=targets.map(baseLabel).join(", "),projectTargets=opts?.globalOnly?[]:targets.map(projectVariantOf).filter((id)=>Boolean(id));if(opts?.globalOnly)log.info(import_picocolors.default.dim("This design lives outside the repository, so only global agent configs are offered \u2014 nothing is written into the checkout."));let mode=await select({message:"Wire the velloo MCP into your coding agents?",options:[{value:"global",label:`Install globally \u2014 ${names}`,hint:"recommended \u2014 one config covers every project"},...projectTargets.length>0?[{value:"project",label:"Install for this project only",hint:projectTargets.map(baseLabel).join(", ")}]:[],{value:"choose",label:"Let me pick agents",hint:"the full list"},{value:"manual",label:"Manual MCP setup instructions",hint:"prints the config to paste into any agent"},{value:"skip",label:"Don't install now",hint:"wire later with `velloo connect`"}],initialValue:"global"});if(isCancel(mode))return null;if(mode==="skip")return{agents:[],manual:!1,preWired};if(mode==="manual")return{agents:[],manual:!0,preWired};if(mode==="global")return{agents:targets,manual:!1,preWired};if(mode==="project")return{agents:projectTargets,manual:!1,preWired};let picked=await pickAgents({exclude:[...opts?.skipWhenCovered?preWired:[],...opts?.globalOnly?Object.values(AGENTS).filter((agent)=>agent.scope==="project").map((agent)=>agent.id):[]],initial:[...new Set([...targets,...preWired,...projectWired])],wired:
|
|
39
|
+
`)}function baseLabel(id){return(AGENTS[id]?.label??id).replace(/\s*\((?:project|global)\)$/,"")}function projectVariantOf(id){let agent=AGENTS[id];if(!agent)return;if(agent.scope==="project")return id;let candidate=id.replace(/-global$/,"");return candidate!==id&&AGENTS[candidate]?candidate:void 0}async function askAgentWiring(opts){let preWired=await globallyWiredAgents(),projectWired=opts?.projectRoot&&!opts.globalOnly?await projectWiredAgents(opts.projectRoot):[],detected=detectInstalledAgents(),fallback=detected.length===0,wanted=fallback?GLOBAL_AGENT_IDS:detected,missing=wanted.filter((id)=>!preWired.includes(id)),alreadyWired=[...new Set([...preWired,...projectWired])];if(projectWired.length>0)log.info(`Already wired for this project: ${projectWired.map(baseLabel).join(", ")} ${import_picocolors.default.dim("(re-wiring is a no-op merge)")}`);if(preWired.length>0){let labels=preWired.map(baseLabel).join(", ");if(opts?.skipWhenCovered&&missing.length===0)return log.success(`velloo is already wired globally \u2014 ${labels} ${import_picocolors.default.dim("(covers this project; skipping agent setup)")}`),{agents:[],manual:!1,preWired:alreadyWired};log.info(`Already wired globally: ${labels} ${import_picocolors.default.dim("(covers this project)")}`)}log.info(fallback?`No coding agents detected on this machine \u2014 offering the defaults (${wanted.map(baseLabel).join(", ")}).`:`Coding agents detected: ${detected.map(baseLabel).join(", ")}`);let targets=missing.length>0?missing:wanted,names=targets.map(baseLabel).join(", "),projectTargets=opts?.globalOnly?[]:targets.map(projectVariantOf).filter((id)=>Boolean(id));if(opts?.globalOnly)log.info(import_picocolors.default.dim("This design lives outside the repository, so only global agent configs are offered \u2014 nothing is written into the checkout."));let mode=await select({message:"Wire the velloo MCP into your coding agents?",options:[{value:"global",label:`Install globally \u2014 ${names}`,hint:"recommended \u2014 one config covers every project"},...projectTargets.length>0?[{value:"project",label:"Install for this project only",hint:projectTargets.map(baseLabel).join(", ")}]:[],{value:"choose",label:"Let me pick agents",hint:"the full list"},{value:"manual",label:"Manual MCP setup instructions",hint:"prints the config to paste into any agent"},{value:"skip",label:"Don't install now",hint:"wire later with `velloo connect`"}],initialValue:"global"});if(isCancel(mode))return null;if(mode==="skip")return{agents:[],manual:!1,preWired:alreadyWired};if(mode==="manual")return{agents:[],manual:!0,preWired:alreadyWired};if(mode==="global")return{agents:targets,manual:!1,preWired:alreadyWired};if(mode==="project")return{agents:projectTargets,manual:!1,preWired:alreadyWired};let picked=await pickAgents({exclude:[...opts?.skipWhenCovered?preWired:[],...opts?.globalOnly?Object.values(AGENTS).filter((agent)=>agent.scope==="project").map((agent)=>agent.id):[]],initial:[...new Set([...targets,...preWired,...projectWired])],wired:alreadyWired});if(picked===null)return null;return{agents:picked.filter((id)=>id!==MANUAL_AGENT_ID),manual:picked.includes(MANUAL_AGENT_ID),preWired:alreadyWired}}async function pickAgents(opts){let exclude=new Set(opts?.exclude??[]),wired=new Set(opts?.wired??[]),agents=Object.values(AGENTS).filter((a)=>!exclude.has(a.id)).sort((a,b)=>a.scope===b.scope?0:a.scope==="global"?-1:1),detected=detectInstalledAgents(),initial=(opts?.initial??(detected.length>0?detected:GLOBAL_AGENT_IDS)).filter((id)=>!exclude.has(id)),picked=await multiselect({message:"Wire the velloo MCP into which agents?",options:[...agents.map((a)=>({value:a.id,label:a.label,hint:wired.has(a.id)?`already wired \u2014 ${a.path(".","~")}`:a.path(".","~")})),{value:MANUAL_AGENT_ID,label:"Manual / other agent",hint:"prints the MCP config to paste anywhere"}],initialValues:initial,required:!1});return isCancel(picked)?null:picked}function hasVellooEntry(content,format){try{switch(format){case"json":{let parsed=JSON.parse(content);return Boolean(parsed.mcpServers&&"velloo"in parsed.mcpServers)}case"codex-toml":return/^\s*\[\s*mcp_servers\s*\.\s*(?:"velloo"|velloo)\s*(?:\]|\.)/m.test(content);case"continue-block":return!0;case"continue-config":{let doc=Bun.YAML.parse(content);return Array.isArray(doc?.mcpServers)&&doc.mcpServers.some((s)=>s!==null&&typeof s==="object"&&s.name==="velloo")}case"opencode-json":{let parsed=JSON.parse(content);return Boolean(parsed.mcp&&"velloo"in parsed.mcp)}case"vscode-json":{let parsed=JSON.parse(content);return Boolean(parsed.servers&&"velloo"in parsed.servers)}}}catch{return!1}}async function projectWiredAgents(projectRoot){let out=[];for(let agent of Object.values(AGENTS)){if(agent.scope!=="project")continue;let content;try{content=await readFile3(agent.path(projectRoot,homedir4()),"utf8")}catch{continue}if(hasVellooEntry(content,agent.format))out.push(agent.id)}return out}async function globallyWiredAgents(homeDir=homedir4()){let out=[];for(let agent of Object.values(AGENTS)){if(agent.scope!=="global")continue;let content;try{content=await readFile3(agent.path(".",homeDir),"utf8")}catch{continue}if(hasVellooEntry(content,agent.format))out.push(agent.id)}return out}function localDesignScopeProblem(folder,agents){if(!localDesignOf(folder))return null;let refused=agents.filter((id)=>AGENTS[id]?.scope==="project");if(refused.length===0)return null;let globals=refused.map((id)=>`${id}-global`).filter((id)=>AGENTS[id]).join(", ");return`${refused.join(", ")} ${refused.length===1?"writes":"write"} into the checkout, and ${folder} is a local design outside it \u2014 only global configs are allowed${globals?` (use ${globals})`:""}.`}async function connect(opts){let transport=opts.transport??"stdio",homeDir=opts.homeDir??homedir4(),projectRoot=await resolveProjectRoot(opts.designFolder,opts.projectRoot),local=localDesignOf(opts.designFolder)!==null,httpUrl=opts.mcpUrl;if(transport==="http"&&!httpUrl)throw Error("the HTTP transport needs the server URL: run `velloo mcp --http` and pass the URL it prints as --mcp-url");let designArg=await designArgumentFor(projectRoot,opts.designFolder),connectionFor=(agent)=>{if(transport==="http")return{transport:"http",url:httpUrl};if(agent.gui){let script=process.argv[1]?resolve2(process.argv[1]):Bun.which("velloo");return script?{transport:"stdio",command:process.execPath,args:[script,"mcp",resolve2(opts.designFolder)]}:{transport:"stdio",command:"velloo",args:["mcp",resolve2(opts.designFolder)]}}return{transport:"stdio",command:"velloo",args:agent.scope==="project"&&designArg?["mcp",designArg]:["mcp"]}},configs=[],unknownAgents=[],projectScoped=[];for(let id of opts.agents){let agent=AGENTS[id];if(!agent){unknownAgents.push(id);continue}if(local&&agent.scope==="project"){projectScoped.push(id);continue}configs.push(await writeAgentConfig(projectRoot,agent,connectionFor(agent),homeDir))}let hasFamily=(family)=>opts.agents.some((id)=>AGENTS[id]?.family===family),hasAny=(...ids)=>opts.agents.some((id)=>ids.includes(id)),plugin=opts.installSkill&&hasFamily("claude-code")?await installClaudePlugin(homeDir)??void 0:void 0,skills=opts.installSkill&&!local&&hasAny("opencode","opencode-global","droid-global","cline-global","gemini","gemini-global","codex","codex-global")?await installSkills(projectRoot):void 0,geminiExtension=opts.installSkill&&hasAny("gemini","gemini-global")?await installGeminiExtension(homeDir)??void 0:void 0,cursorRules=opts.installSkill&&!local&&hasFamily("cursor")?await installCursorRules(projectRoot,opts.designFolder):void 0;return{projectRoot,transport,configs,skills,plugin,geminiExtension,cursorRules,unknownAgents,projectScoped}}
|
|
40
40
|
export{AGENTS,AGENT_IDS,PROJECT_AGENT_IDS,GLOBAL_AGENT_IDS,resolveProjectRoot,designArgumentFor,agentRootCandidates,writeAgentConfig,refreshAgentArtifacts,MANUAL_AGENT_ID,manualSetupText,askAgentWiring,globallyWiredAgents,localDesignScopeProblem,connect};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{isCancel,confirm,select}from"./chunk-80fjfyha.js";import{capturesDir,sessionStatePath,captureDir,listCaptures,deleteCapture,isSafeCaptureId,HeadedBrowserMissingError,startCaptureSession}from"./chunk-
|
|
2
|
+
import{isCancel,confirm,select}from"./chunk-80fjfyha.js";import{capturesDir,sessionStatePath,captureDir,listCaptures,deleteCapture,isSafeCaptureId,HeadedBrowserMissingError,startCaptureSession}from"./chunk-ngbqd7kb.js";import"./chunk-4pkw6d9n.js";import{loadDesignFolder}from"./chunk-66qgm91k.js";import"./chunk-afe25dpr.js";import{fail}from"./chunk-s23gtejf.js";import{installChromiumInteractive}from"./chunk-5ma8zbty.js";import{openUrl}from"./chunk-hggap8fq.js";import{DESIGN_ARG_DESCRIPTION,resolveDesign}from"./chunk-675tr675.js";import{withSubcommands}from"./chunk-94vx1yb0.js";import{require_picocolors}from"./chunk-jrsknzjt.js";import{__toESM,defineCommand}from"./chunk-2smsg2ct.js";import{join}from"path";var import_picocolors=__toESM(require_picocolors(),1);var CTRL_C=3,LOOPBACK=/^(localhost|127\.0\.0\.1|\[::1\]|::1)(:\d+)?$/i;function normalizeCaptureUrl(input){let raw=input.trim();if(raw==="")return null;let hasScheme=/^[a-z][a-z0-9+.-]*:\/\//i.test(raw),host=raw.split(/[/?#]/,1)[0]??"",candidate=hasScheme?raw:`${LOOPBACK.test(host)?"http":"https"}://${raw}`;try{let url=new URL(candidate);if(url.protocol!=="http:"&&url.protocol!=="https:")return null;if(url.hostname==="")return null;return url.toString()}catch{return null}}function bindKeys(handlers){let stdin=process.stdin;if(!stdin.isTTY)return()=>{};stdin.setRawMode(!0),stdin.resume(),stdin.setEncoding("utf8");let onData=(key)=>{if(key==="c")handlers.page();else if(key==="t")handlers.theme();else if(key==="l")handlers.list();else if(key==="q"||key.charCodeAt(0)===CTRL_C)handlers.quit()};return stdin.on("data",onData),()=>{if(stdin.off("data",onData),stdin.isTTY)stdin.setRawMode(!1);stdin.pause()}}function when(iso){let d=new Date(iso);if(Number.isNaN(d.getTime()))return iso;return d.toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function describe(m){let what=m.themeOnly?"theme only":`${m.nodeCount} nodes, ${m.assetCount} assets`;return`${m.title||m.finalUrl||m.url} ${import_picocolors.default.dim(`\u2014 ${what}, ${when(m.capturedAt)}`)}`}function printCaptures(captures,dir){if(console.log(""),captures.length===0){console.log(import_picocolors.default.dim(" No captures yet. Run `velloo capture <url>` to make one.")),console.log("");return}console.log(` ${import_picocolors.default.bold(`${captures.length} capture${captures.length===1?"":"s"}`)}`),console.log(import_picocolors.default.dim(` ${dir}`)),console.log("");for(let m of captures)console.log(` ${import_picocolors.default.cyan(m.id)} ${describe(m)}`),console.log(import_picocolors.default.dim(` ${m.finalUrl||m.url}`));console.log("")}async function browseCaptures(root,folderId){let dir=capturesDir(root,folderId),captures=listCaptures(root,folderId);if(!process.stdin.isTTY||captures.length===0){printCaptures(captures,dir);return}for(;;){if(captures=listCaptures(root,folderId),captures.length===0){console.log(import_picocolors.default.dim(`
|
|
3
3
|
No captures left.
|
|
4
4
|
`));return}let picked=await select({message:`${captures.length} capture${captures.length===1?"":"s"} for this folder`,options:[...captures.map((m)=>({value:m.id,label:describe(m),hint:m.finalUrl||m.url})),{value:"__done",label:import_picocolors.default.dim("Done")}]});if(isCancel(picked)||picked==="__done")return;let manifest=captures.find((m)=>m.id===picked);if(!manifest)continue;let action=await select({message:manifest.title||manifest.id,options:[...manifest.files.includes("page.png")?[{value:"open",label:"Open the screenshot"}]:[],{value:"details",label:"Show what it contains"},{value:"delete",label:import_picocolors.default.red("Delete this capture")},{value:"back",label:import_picocolors.default.dim("Back")}]});if(isCancel(action)||action==="back")continue;if(action==="open"){await openUrl(`file://${join(captureDir(root,manifest.id,folderId),"page.png")}`);continue}if(action==="details"){console.log(""),console.log(` ${import_picocolors.default.bold(manifest.id)}`),console.log(` url ${manifest.finalUrl||manifest.url}`),console.log(` captured ${when(manifest.capturedAt)}`),console.log(` viewport ${manifest.viewport.w}\xD7${manifest.viewport.h}`),console.log(` nodes ${manifest.nodeCount}`),console.log(` assets ${manifest.assetCount}`),console.log(` files ${manifest.files.join(", ")}`),console.log(import_picocolors.default.dim(` ${captureDir(root,manifest.id,folderId)}`)),console.log(""),console.log(import_picocolors.default.dim(` verify a screen against it: compare_to_url { captureId: "${manifest.id}" }`)),console.log("");continue}if(action==="delete"){let sure=await confirm({message:`Delete ${manifest.title||manifest.id}? This can't be undone.`,initialValue:!1});if(isCancel(sure)||!sure)continue;deleteCapture(root,manifest.id,folderId),console.log(import_picocolors.default.dim(` deleted ${manifest.id}`))}}}var DESIGN_FLAG={type:"string",description:DESIGN_ARG_DESCRIPTION};async function captureDesign(arg,command){let design=await loadDesignFolder(await resolveDesign(arg,command,{designFlag:"--design"}));return{root:design.root,folderId:design.config.folderId}}var list=defineCommand({meta:{name:"list",description:"Browse stored captures (interactive on a TTY)"},args:{design:DESIGN_FLAG},async run({args}){let{root,folderId}=await captureDesign(args.design,"capture list");await browseCaptures(root,folderId)}}),remove=defineCommand({meta:{name:"delete",description:"Delete a stored capture, or every capture for the design"},args:{id:{type:"positional",required:!0,description:"The capture id, or `all`"},design:DESIGN_FLAG},async run({args}){let{root,folderId}=await captureDesign(args.design,"capture delete"),id=args.id.trim();if(id==="all"){let all=listCaptures(root,folderId);if(all.length===0){console.log(import_picocolors.default.dim("velloo capture: no captures to delete."));return}if(process.stdin.isTTY){let sure=await confirm({message:`Delete all ${all.length} captures for this design? This can't be undone.`,initialValue:!1});if(isCancel(sure)||!sure){console.log(import_picocolors.default.dim(" nothing deleted."));return}}for(let m of all)deleteCapture(root,m.id,folderId);console.log(`velloo capture: deleted ${all.length} captures.`);return}if(!isSafeCaptureId(id))fail("capture delete",`not a capture id: ${args.id}`);if(!deleteCapture(root,id,folderId))fail("capture delete",`no capture "${id}" for this design \u2014 run \`velloo capture list\`.`);console.log(`velloo capture: deleted ${id}.`)}}),capture=defineCommand({meta:{name:"capture",description:"open a browser, log in, and capture pages as evidence for the agent"},args:{url:{type:"positional",required:!1,description:"Page to open the browser on, e.g. http://localhost:3000/dashboard"},design:DESIGN_FLAG},async run({args}){let{root,folderId}=await captureDesign(args.design,"capture"),url;if(args.url!==void 0){let normalized=normalizeCaptureUrl(args.url);if(normalized===null)fail("capture",`not a page that can be captured: ${args.url} (expected an http(s) URL)`);url=normalized}let statePath=url?sessionStatePath(root,new URL(url).origin,folderId):void 0;console.log(""),console.log(` ${import_picocolors.default.bold("velloo capture")}`),console.log(import_picocolors.default.dim(` A browser window opens. Log in and navigate wherever you need \u2014 then use the
|
|
5
5
|
`+" velloo toolbar in the page (or the keys below) to capture what the agent should see.")),console.log(""),console.log(import_picocolors.default.dim(` ${import_picocolors.default.cyan("c")} capture page ${import_picocolors.default.cyan("t")} capture theme ${import_picocolors.default.cyan("l")} list ${import_picocolors.default.cyan("q")} done`)),console.log("");let made=[],onCapture=(m)=>{made.push(m);let what=m.themeOnly?"theme":`${m.nodeCount} nodes, ${m.assetCount} assets`;console.log(` ${import_picocolors.default.green("\u2713")} ${m.title||m.url} ${import_picocolors.default.dim(`(${what}) \u2192 ${m.id}`)}`)},start=async()=>startCaptureSession({folderRoot:root,...folderId?{folderId}:{},...url?{url}:{},...statePath?{sessionStatePath:statePath}:{},onCapture,onStatus:(message)=>console.log(import_picocolors.default.dim(` ${message}`))}),session;try{session=await start()}catch(err){if(!(err instanceof HeadedBrowserMissingError))throw err;if(!process.stdin.isTTY)fail("capture",err.message);console.log(""),console.log(` ${import_picocolors.default.yellow("\u26A0")} ${err.message}`);let proceed=await confirm({message:"Download the full browser now? (~300MB, one-time)",initialValue:!0});if(isCancel(proceed)||!proceed)fail("capture","no browser available for a capture session.");if(!await installChromiumInteractive())fail("capture","the browser isn't usable yet \u2014 see the output above.");session=await start()}let report=(e)=>{console.log(import_picocolors.default.red(` capture failed: ${e instanceof Error?e.message:String(e)}`))},unbind=bindKeys({page:()=>{session.capturePage().catch(report)},theme:()=>{session.captureTheme().catch(report)},list:()=>{if(console.log(import_picocolors.default.dim(` on: ${session.currentUrl()||"(no page)"}`)),made.length===0)console.log(import_picocolors.default.dim(" nothing captured yet"));for(let m of made)console.log(import_picocolors.default.dim(` ${m.id} ${m.title||m.url}`))},quit:()=>{session.close()}});if(await session.finished,unbind(),console.log(""),made.length===0)console.log(` ${import_picocolors.default.dim("No captures made.")}`);else console.log(` ${import_picocolors.default.green("\u2713")} ${made.length} capture${made.length===1?"":"s"} saved`),console.log(import_picocolors.default.dim(` ${capturesDir(root,folderId)}`)),console.log(""),console.log(import_picocolors.default.dim(" The agent can read these with list_captures / get_capture,")),console.log(import_picocolors.default.dim(" and verify a screen against one with compare_to_url { captureId }."));if(statePath)console.log(""),console.log(import_picocolors.default.dim(" Session cookies for this app were saved to:")),console.log(import_picocolors.default.dim(` ${statePath}`)),console.log(import_picocolors.default.dim(` Scoped to that site only (third-party sign-in cookies are dropped), outside your
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var package_default={name:"@velloo/cli",version:"0.5.0",license:"Apache-2.0",private:!0,type:"module",description:"Velloo CLI",bin:{velloo:"./src/cli.ts"},exports:{".":"./src/cli.ts"},engines:{bun:">=1.4.0"},scripts:{build:"bun run build.ts"},dependencies:{"@clack/prompts":"1.7.0","@velloo/canvas":"workspace:*","@velloo/codegen":"workspace:*","@velloo/protocol":"workspace:*","@velloo/provider":"workspace:*","@velloo/provider-antd":"workspace:*","@velloo/provider-chakra":"workspace:*","@velloo/provider-html":"workspace:*","@velloo/provider-mui":"workspace:*","@velloo/provider-none":"workspace:*","@velloo/renderer":"workspace:*","@velloo/result":"workspace:*","@velloo/schema":"workspace:*","@velloo/server":"workspace:*","@velloo/shadcn-snapshot":"workspace:*",citty:"0.2.2",picocolors:"1.1.1",zod:"4.5.4"}};var PACKAGE_VERSION=package_default.version,TOOL_VERSION="0.5.0",BUILD_TIME_MS=1790768649755;
|
|
3
|
+
export{PACKAGE_VERSION,TOOL_VERSION,BUILD_TIME_MS};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{ok,err,PROTECTED_SHARES_UNAVAILABLE,PublishDestinationsResponseSchema,LinkResponseSchema,LinkAccessResponseSchema,VersionUploadResponseSchema,PublishedDesignsResponseSchema,TeamsResponseSchema,unreachable,protocolViolation,uploadRaceLost,readFailure,cloudFailure,httpFailureFrom,boardLimitFrom,describeBoardLimit,describeCloudError,cloudJson,cloudFetch}from"./chunk-afe25dpr.js";import{prettifyError,string,number,boolean,array,object,strictObject,record,literal,AnnotationSchema,CanvasNoteSchema,BoardSchema,ExtensionSchema,ViewportPresetSchema,LibrarySchema,ScreenSchema,SnippetSchema,sanitizeSvgMarkup,ThemeSchema,ViewportSchema}from"./chunk-4pkw6d9n.js";import{buildBoardComposite,MAX_CONCURRENT_RENDERS,renderScreen,captureScreenshot}from"./chunk-ngbqd7kb.js";import{writeJsonAtomic,managedDesignId,localDesignOf,recordedDesignName,orderedBoards,activeBoards,registryForScreen,renderPassForScreen,hostStylesheetsForScreen,liveExtensions,LiveBundler,hostFilesFetch,storedHostSource,shipHostFiles,createPublishMount}from"./chunk-66qgm91k.js";import{withAssetServer}from"./chunk-vgx47anc.js";import{checkCloudHealth}from"./chunk-edgwx026.js";var VERSION_UPLOAD_TIMEOUT_MS=60000,VERSION_UPLOAD_RETRIES=1;async function listPublishDestinations(opts){let query=new URLSearchParams({folderId:opts.folderId});if(opts.teamId)query.set("teamId",opts.teamId);return cloudFetch(`${opts.baseUrl}/v1/publish-destinations?${query}`,PublishDestinationsResponseSchema,{operation:"listing publish destinations",token:opts.token})}async function uploadLinkBundle(opts){let{baseUrl,token,form}=opts,authorized={authorization:`Bearer ${token}`},createRes=await fetch(`${baseUrl}/v1/links`,{method:"POST",headers:authorized,body:JSON.stringify(opts.link)}).catch((error)=>error);if(!(createRes instanceof Response))return err(unreachable(createRes,{url:baseUrl}));if(createRes.status!==200&&createRes.status!==201)return err(await httpFailureFrom("link creation",createRes));let created=createRes.status===201,createdLink=await cloudJson(createRes,LinkResponseSchema,"link creation");if(!createdLink.ok)return createdLink;let link=createdLink.value;if(opts.link.publishMode==="new"&&!created)return err(protocolViolation("the cloud reused a link even though a new publish destination was requested"));if(opts.link.publishMode==="update"&&created)return await fetch(`${baseUrl}/v1/links/${link.slug}`,{method:"DELETE",headers:authorized}).catch(()=>{}),err(protocolViolation("the cloud created a link even though an existing destination was selected"));if(!created){let accessRes=await fetch(`${baseUrl}/v1/links/${link.slug}/access`,{method:"PUT",headers:{...authorized,"content-type":"application/json"},body:JSON.stringify({visibility:opts.link.visibility,password:opts.link.password??null,passwordExpiresAt:opts.link.password?opts.link.passwordExpiresAt??null:null,expectedVersionId:opts.link.expectedVersionId??null,...opts.link.audience?.length?{audience:opts.link.audience}:{},...opts.link.publicComments!==void 0?{publicComments:opts.link.publicComments}:{}})});if(!accessRes.ok)return err(await httpFailureFrom("privacy update",accessRes));let access=await cloudJson(accessRes,LinkAccessResponseSchema,"privacy update");if(!access.ok)return access;link={...link,...access.value}}if(form.append("publishMode",opts.link.publishMode),opts.link.expectedVersionId)form.append("expectedVersionId",opts.link.expectedVersionId);let attempt=await uploadVersionWithRetry({url:`${baseUrl}/v1/links/${link.slug}/versions`,headers:authorized,form,timeoutMs:opts.uploadTimeoutMs??VERSION_UPLOAD_TIMEOUT_MS,retries:opts.uploadRetries??VERSION_UPLOAD_RETRIES});if(!attempt.ok)return attempt;let upload=attempt.value,uploadRes=upload.response;if(uploadRes.status!==201){let{detail,code}=await readFailure(uploadRes);if(upload.transientFailures>0&&uploadRes.status===409&&/slot changed|already has a version/i.test(detail))return err(uploadRaceLost());if(created)await fetch(`${baseUrl}/v1/links/${link.slug}`,{method:"DELETE",headers:authorized}).catch(()=>{});return err(cloudFailure("upload",uploadRes.status,detail,code))}let parsed=await cloudJson(uploadRes,VersionUploadResponseSchema,"upload");if(!parsed.ok)return parsed;let uploaded=parsed.value;return ok({link:{slug:link.slug,visibility:link.visibility??opts.link.visibility,passwordProtected:link.passwordProtected??opts.link.password!=null,...link.audience!==void 0?{audience:link.audience}:{},...link.publicComments!==void 0?{publicComments:link.publicComments}:{}},created,files:uploaded.files,bytes:uploaded.bytes,shareUrl:uploaded.url.startsWith("http")?uploaded.url:`${baseUrl}${uploaded.url}`,...uploaded.tier!==void 0?{tier:uploaded.tier}:{},...uploaded.history!==void 0?{history:uploaded.history}:{}})}async function uploadVersionWithRetry(opts){let attempts=Math.max(1,Math.floor(opts.retries)+1),transientFailures=0,lastNetworkError="unknown network error",lastTimedOut=!1;for(let attempt=1;attempt<=attempts;attempt+=1){let controller=new AbortController,timedOut=!1,timer=setTimeout(()=>{timedOut=!0,controller.abort()},Math.max(1,opts.timeoutMs));try{let response=await fetch(opts.url,{method:"POST",headers:opts.headers,body:opts.form,signal:controller.signal});if(response.status<500||attempt===attempts)return ok({response,transientFailures});transientFailures+=1,await response.body?.cancel().catch(()=>{})}catch(error){if(transientFailures+=1,lastTimedOut=timedOut,lastNetworkError=error instanceof Error?error.message:String(error),attempt===attempts){let retried=attempts>1?` after ${attempts} attempts`:"";return err(timedOut?unreachable(`timed out after ${formatDuration(opts.timeoutMs)}${retried}`,{url:opts.url,timedOut:!0}):unreachable(`version upload${retried}: ${lastNetworkError}`,{url:opts.url}))}}finally{clearTimeout(timer)}}return err(lastTimedOut?unreachable(`timed out after ${formatDuration(opts.timeoutMs)}`,{url:opts.url,timedOut:!0}):unreachable(`version upload: ${lastNetworkError}`,{url:opts.url}))}function formatDuration(ms){return ms>=1000?`${Math.round(ms/1000)}s`:`${Math.max(1,Math.round(ms))}ms`}var cloudUnhealthy=(detail)=>({kind:"CloudUnhealthy",detail}),noScreens=(root)=>({kind:"NoScreens",root}),noBoardScreens=()=>({kind:"NoBoardScreens"}),bundleInvalid=(detail)=>({kind:"BundleInvalid",detail}),teamNotFound=(requested)=>({kind:"TeamNotFound",requested}),teamAmbiguous=(requested)=>({kind:"TeamAmbiguous",requested}),teamNotPublishable=(requested,reviewer)=>({kind:"TeamNotPublishable",requested,reviewer}),noPublishableTeam=(reviewer)=>({kind:"NoPublishableTeam",reviewer}),teamChoiceRequired=(names)=>({kind:"TeamChoiceRequired",names}),REVIEWERS_CANNOT_PUBLISH="reviewers can view and comment on boards, but not publish them \u2014 to publish, ask an owner or admin to make you a member",FREE_PLAN_OWNER_ONLY="on the Free plan only the organization owner publishes \u2014 ask the owner to renew, or leave the organization to publish on your own";function describePublishError(error){switch(error.kind){case"CloudUnhealthy":return error.detail;case"NoScreens":return`no screens found in ${error.root} \u2014 is this a velloo design folder?`;case"NoBoardScreens":return"the selected boards have no screens.";case"BundleInvalid":return`velloo built an invalid design bundle and did not upload it: ${error.detail}. This is a bug \u2014 please report it.`;case"TeamNotFound":return`no team named or identified by '${error.requested}'`;case"TeamAmbiguous":return`more than one team is named '${error.requested}'; pass its UUID`;case"TeamNotPublishable":return error.reviewer?REVIEWERS_CANNOT_PUBLISH:`you can't publish to '${error.requested}' \u2014 you can only publish to teams you're on`;case"NoPublishableTeam":return error.reviewer?REVIEWERS_CANNOT_PUBLISH:FREE_PLAN_OWNER_ONLY;case"TeamChoiceRequired":return`you can publish to more than one team \u2014 choose one (${error.names.join(", ")}) with --team`;case"HttpFailure":{if(error.status===403&&/protected shares/i.test(error.detail))return`${PROTECTED_SHARES_UNAVAILABLE}. Publish publicly, or upgrade your plan from your cloud's billing page`;let limit=boardLimitFrom(error);if(limit)return`${describeBoardLimit(limit)}. Run \`velloo publish list\` to see them, or upgrade your plan from your cloud's billing page`;return describeCloudError(error)}default:return describeCloudError(error)}}import{execFileSync}from"child_process";import{readFile}from"fs/promises";import{join}from"path";var DESIGN_BUNDLE_FORMAT=1,BundleScreenshotsSchema=object({cover:string().optional(),screens:record(string(),string()).optional(),boards:record(string(),string()).optional()}),DesignBundleMetaSchema=object({formatVersion:number().int().positive().optional(),title:string().optional(),screens:array(object({id:string(),name:string().optional()})).optional(),boards:array(object({id:string()})).optional(),screenshots:BundleScreenshotsSchema.optional()});var DesignBundleSchema=strictObject({formatVersion:literal(DESIGN_BUNDLE_FORMAT),title:string(),viewport:ViewportSchema,defaultLibrary:string(),libraries:record(string(),LibrarySchema),extensions:record(string(),ExtensionSchema),viewportPresets:array(ViewportPresetSchema),theme:ThemeSchema,themes:record(string(),ThemeSchema),customCss:string(),snippets:array(SnippetSchema),screens:array(ScreenSchema),boards:array(BoardSchema),annotations:record(string(),array(AnnotationSchema)),notes:record(string(),array(CanvasNoteSchema)),live:boolean(),snapshotCssPath:string(),bundlePath:string().optional(),hostStylesheets:array(string().regex(/^(?:assets\/host\/[A-Za-z0-9._@~+-][A-Za-z0-9._@~+/-]*\.css|https:\/\/\S+)$/)).optional(),screenshots:object({cover:string(),screens:record(string(),string()),boards:record(string(),string())}).optional()});import{homedir}from"os";import{delimiter,dirname,resolve}from"path";function designGitEnv(folder){let ceilings=[homedir()];if(managedDesignId(folder)||localDesignOf(folder))ceilings.push(dirname(resolve(folder)));let inherited=process.env.GIT_CEILING_DIRECTORIES;if(inherited)ceilings.push(inherited);return{...process.env,GIT_CEILING_DIRECTORIES:ceilings.join(delimiter)}}var MAX_SCREENSHOT_BYTES=4194304,MAX_SCREENSHOT_COUNT=120,DOWNSCALE_WIDTH=1280;function isBrowserMissing(err){return err instanceof Error&&err.name==="BrowserMissingError"}async function captureBundleScreenshots(opts){let{screens,boards,viewport,renderHtml,capture,warn,progress}=opts,selectedScreens=opts.screenIds?screens.filter((screen)=>opts.screenIds?.has(screen.id)):screens,selectedBoards=opts.boardIds?boards.filter((board)=>opts.boardIds?.has(board.id)):boards,manifest={cover:"",screens:{},boards:{}},total=selectedScreens.length+selectedBoards.filter((board)=>board.frames.length>0).length,done=0,advance=()=>{done+=1,progress?.(done,total)},shoot=async(html,vp,fullPage)=>{let png=await capture({html,viewport:vp,fullPage,deviceScaleFactor:1});if(png.byteLength>MAX_SCREENSHOT_BYTES&&DOWNSCALE_WIDTH<vp.w)png=await capture({html,viewport:vp,fullPage,deviceScaleFactor:DOWNSCALE_WIDTH/vp.w});return png.byteLength<=MAX_SCREENSHOT_BYTES?png:null},jobs=[];for(let screen of selectedScreens)jobs.push({run:async()=>{try{let png=await shoot(await renderHtml(screen),viewport,!0);if(!png)return warn(`screenshot of screen "${screen.id}" exceeds 4MB even downscaled \u2014 skipped`),null;return{kind:"screen",id:screen.id,path:`screenshots/${screen.id}.png`,bytes:png}}catch(err){if(isBrowserMissing(err))throw err;return warn(`screenshot of screen "${screen.id}" failed \u2014 skipped (${message(err)})`),null}finally{advance()}}});let screenById=new Map(screens.map((screen)=>[screen.id,screen]));for(let board of selectedBoards){if(board.frames.length===0)continue;jobs.push({run:async()=>{try{let framed=(await Promise.all(board.frames.map(async(frame)=>{let screen=screenById.get(frame.screen);return screen?{frame,html:await renderHtml(screen,board.theme,frame.scheme??"light")}:null}))).filter((entry)=>entry!==null);if(framed.length===0)return null;let composite=buildBoardComposite(framed),png=await shoot(composite.html,composite.viewport,!1);if(!png)return warn(`screenshot of board "${board.id}" exceeds 4MB even downscaled \u2014 skipped`),null;return{kind:"board",id:board.id,path:`screenshots/${board.id}.png`,bytes:png}}catch(err){if(isBrowserMissing(err))throw err;return warn(`screenshot of board "${board.id}" failed \u2014 skipped (${message(err)})`),null}finally{advance()}}})}let shotLimit=MAX_SCREENSHOT_COUNT-1,results=Array.from({length:jobs.length},()=>null),nextJob=0,completedShots=0,reservedShots=0,browserMissing=!1,worker=async()=>{while(!browserMissing&&nextJob<jobs.length&&completedShots+reservedShots<shotLimit){let index=nextJob++;reservedShots+=1;try{let shot=await jobs[index]?.run();if(shot)results[index]=shot,completedShots+=1}catch(err){if(isBrowserMissing(err))browserMissing=!0;else throw err}finally{reservedShots-=1}}},concurrency=Math.max(1,Math.min(MAX_CONCURRENT_RENDERS,Math.floor(opts.concurrency??MAX_CONCURRENT_RENDERS)));if(await Promise.all(Array.from({length:Math.min(concurrency,jobs.length)},worker)),browserMissing)return warn("no headless browser available \u2014 publishing without screenshots"),null;if(nextJob<jobs.length)warn(`screenshot limit (${MAX_SCREENSHOT_COUNT}) reached \u2014 remaining shots skipped`);let files=[];for(let shot of results){if(!shot)continue;if(files.push({path:shot.path,bytes:shot.bytes}),shot.kind==="screen")manifest.screens[shot.id]=shot.path;else manifest.boards[shot.id]=shot.path}let coverSource=selectedBoards.map((b)=>manifest.boards[b.id]).find((p)=>p!==void 0)??selectedScreens.map((s)=>manifest.screens[s.id]).find((p)=>p!==void 0);if(!coverSource)return null;let source=files.find((f)=>f.path===coverSource);if(!source)return null;return manifest.cover="screenshots/cover.png",files.push({path:manifest.cover,bytes:source.bytes}),{manifest,files}}function message(err){let msg=err instanceof Error?err.message:String(err);return msg.split(`
|
|
3
|
+
`)[0]??msg}var normalizedBoardIds=(boardIds)=>[...new Set(boardIds)].sort();function publishSlotMismatches(slot,current){let mismatches=[];if(!slot.context.contextKnown)mismatches.push("older publish has no board-selection context");if(!(current.candidateTeamIds?slot.teamId!==null&¤t.candidateTeamIds.includes(slot.teamId):slot.teamId===current.teamId))mismatches.push("team differs");if(JSON.stringify(normalizedBoardIds(slot.context.boardIds))!==JSON.stringify(normalizedBoardIds(current.boardIds)))mismatches.push("board selection differs");if(slot.context.repo!==current.repo)mismatches.push("repository differs");else if(current.repo!==null){if(!slot.context.branch||!current.branch)mismatches.push("branch is unavailable");else if(slot.context.branch!==current.branch)mismatches.push("branch differs")}return mismatches}function recommendedPublishSlot(slots,current){return exactPublishSlots(slots,current)[0]??null}function exactPublishSlots(slots,current){return slots.filter((slot)=>publishSlotMismatches(slot,current).length===0).sort((left,right)=>publishedAt(right.lastPublishedAt)-publishedAt(left.lastPublishedAt))}var publishedAt=(value)=>{let parsed=value?Date.parse(value):Number.NaN;return Number.isFinite(parsed)?parsed:Number.NEGATIVE_INFINITY},PUBLISH_VIEWPORT={w:1440,h:900};function defaultPublishTitle(folderRoot){return`${recordedDesignName(folderRoot)??folderRoot.split("/").filter(Boolean).pop()} designs`}async function ensureFolderId(folder,existing,report){if(existing)return existing;let path=join(folder,".design","config.json"),raw=JSON.parse(await readFile(path,"utf8")),folderId=crypto.randomUUID();return await writeJsonAtomic(path,{...raw,folderId}),report({kind:"note",message:"assigned this folder a cloud id (.design/config.json folderId) \u2014 commit it so every clone finds the folder's share links."}),folderId}function reportProvenance(git,report){if(git.repo){report({kind:"info",message:git.branch?`source: ${git.repo} on ${git.branch}`:`source: ${git.repo} (detached HEAD \u2014 no branch recorded)`});return}report({kind:"info",message:git.branch?`source: branch ${git.branch} \u2014 no git remote, publishing without a repository`:"source: not a git repository \u2014 publishing without repository or branch"})}function gitProbe(folder){return{encoding:"utf8",stdio:["ignore","pipe","ignore"],env:designGitEnv(folder)}}function gitCommitSha(folder){try{let probe=gitProbe(folder);if(execFileSync("git",["-C",folder,"status","--porcelain"],probe).trim().length>0)return null;return execFileSync("git",["-C",folder,"rev-parse","HEAD"],probe).trim()}catch{return null}}function normalizeRemote(url){let rest=url.trim().replace(/\.git\/?$/,"");if(/^[./~]/.test(rest)||/^[a-z]:[\\/]/i.test(rest)||rest.startsWith("file://"))return null;let scp=/^(?:[\w.-]+@)?([\w.-]+):(?!\/\/)(.+)$/.exec(rest);if(scp?.[1]&&scp[2])return`${scp[1]}/${scp[2].replace(/^\/+/,"")}`;try{let parsed=new URL(rest.includes("://")?rest:`https://${rest}`);if(!parsed.hostname||!parsed.pathname||parsed.pathname==="/")return null;return`${parsed.hostname}${parsed.pathname.replace(/\/+$/,"")}`}catch{return null}}function gitContext(folder){let probe=gitProbe(folder),run=(args)=>{try{return execFileSync("git",["-C",folder,...args],probe).trim()||null}catch{return null}},branchRaw=run(["rev-parse","--abbrev-ref","HEAD"]),remote=run(["remote","get-url","origin"]);return{repo:remote?normalizeRemote(remote):null,branch:branchRaw==="HEAD"?null:branchRaw}}function publishableTeams(teams){return teams.filter((team)=>team.canPublish!==!1)}function resolvePublishTeam(teams,requested){if(teams.length===0)return requested?err(teamNotFound(requested)):ok({kind:"personal"});let reviewer=teams.some((team)=>team.role==="reviewer"),publishable=publishableTeams(teams);if(requested){let named=(candidates)=>candidates.filter((team)=>team.id===requested||team.name.toLowerCase()===requested.toLowerCase()),exact=named(publishable);if(exact.length>1)return err(teamAmbiguous(requested));let[team]=exact;if(team)return ok({kind:"team",team});return err(named(teams).length>0?teamNotPublishable(requested,reviewer):publishable.length===0?noPublishableTeam(reviewer):teamNotFound(requested))}let[only,...rest]=publishable;if(!only)return err(noPublishableTeam(reviewer));return rest.length===0?ok({kind:"team",team:only}):ok({kind:"choose",teams:publishable})}async function listTeams(baseUrl,token){let body=await cloudFetch(`${baseUrl}/v1/teams/mine`,TeamsResponseSchema,{operation:"listing teams",token});return body.ok?ok(body.value.teams):body}function selectBoards(design,boardIds){if(!boardIds||boardIds.length===0)return activeBoards(design).map(([,board])=>board);let wanted=new Set(boardIds);return orderedBoards(design).map(([,board])=>board).filter((board)=>wanted.has(board.id))}async function publishDesign(cloud,pipeline,request,report=()=>{}){let{folder:design}=pipeline,{root,config}=design;report({kind:"step",step:"check",message:"checking the cloud"});let health=await checkCloudHealth(cloud.baseUrl);if(health.status==="unreachable")return err(unreachable(health.reason,{url:cloud.baseUrl}));if(health.status==="unhealthy")return err(cloudUnhealthy(health.detail));if(design.screens.size===0)return err(noScreens(root));report({kind:"step",step:"select",message:"collecting boards"});let boards=selectBoards(design,request.boardIds),screenIds=Boolean(request.boardIds&&request.boardIds.length>0)?new Set(boards.flatMap((b)=>b.frames.map((f)=>f.screen))):null,screens=screenIds?[...design.screens.values()].filter((s)=>screenIds.has(s.id)):[...design.screens.values()];if(screens.length===0)return err(noBoardScreens());let viewport=request.viewport??PUBLISH_VIEWPORT,title=request.title?.trim()||defaultPublishTitle(root),form=new FormData,bundleFiles=0,bundleBytes=0,addFile=(path,data,type)=>{if(typeof data==="string"?data.length===0:data.byteLength===0)return report({kind:"warn",message:`skipped empty file: ${path}`}),!1;return form.append("file",new File([data],path,type?{type}:void 0)),bundleFiles+=1,bundleBytes+=typeof data==="string"?new TextEncoder().encode(data).byteLength:data.byteLength,!0},liveExt=liveExtensions(config.extensions),bundler=Object.keys(liveExt).length>0?pipeline.liveBundler??createPublishBundler(root,config):null;report({kind:"step",step:"styles",message:"compiling styles"});let snapshotCss=await pipeline.snapshotCss(),live=!1,liveCode=null;if(bundler){report({kind:"step",step:"bundle",message:"bundling live components"});let bundle=await bundler.build();for(let e of bundle.errors)report({kind:"warn",message:`live-island: ${e.message}`});if(addFile("bundle.js",bundle.code,"text/javascript"))liveCode=bundle.code,live=!0}report({kind:"step",step:"capture",message:"capturing previews"});let mount=createPublishMount(pipeline.folder,pipeline.providers,pipeline.defaultProvider),shots=await withAssetServer(root,liveCode,async(baseHref)=>{let htmlCache=new Map,renderHtml=async(screen,themeName,scheme="light")=>{let theme=(themeName?design.themes.get(themeName):void 0)??design.theme,key=`${screen.id}\x00${theme.name}\x00${scheme}`,cached=htmlCache.get(key);if(cached)return cached;let canvasBundle=await mount.forScreen(screen,theme,scheme==="dark"),rendering=renderScreen(screen,theme,{viewport,snapshotCss,registry:registryForScreen(screen,pipeline.providers,pipeline.defaultProvider,config.extensions??{}),renderPass:renderPassForScreen(screen,pipeline.providers,pipeline.defaultProvider,theme),snippets:design.snippets,customCss:design.customCss,dark:scheme==="dark",baseHref,...live?{liveBundleUrl:"/live/bundle.js"}:{},...canvasBundle?{canvasBundle}:{},hostStylesheets:hostStylesheetsForScreen(screen,pipeline.providers,pipeline.defaultProvider,config.hostApp)}).then(({html})=>html);return htmlCache.set(key,rendering),rendering};return await captureBundleScreenshots({screens,boards,...request.screenshotSelection?{screenIds:request.screenshotSelection.screenIds,boardIds:request.screenshotSelection.boardIds}:{},viewport,renderHtml,capture:async(req)=>(await captureScreenshot({html:req.html,viewport:req.viewport,fullPage:req.fullPage,deviceScaleFactor:req.deviceScaleFactor})).png,warn:(message)=>report({kind:"warn",message}),progress:(done,total)=>report({kind:"capture",done,total})})},{bundle:mount.serve,host:hostFilesFetch(()=>root)});for(let f of shots?.files??[])addFile(f.path,f.bytes,"image/png");let host=Object.values(pipeline.providers).some((provider)=>provider.hostStylesheets)?await shipHostFiles({source:storedHostSource(root),stylesheets:config.hostApp?.stylesheets??[],screens,snippets:[...design.snippets.values()],warn:(message)=>report({kind:"warn",message})}):{screens,snippets:[...design.snippets.values()],files:[],stylesheets:[]};for(let f of host.files)addFile(f.path,f.bytes,f.type);let annotations=Object.fromEntries([...design.annotations].filter(([screenId,list])=>list.length>0&&screens.some((s)=>s.id===screenId))),notes=Object.fromEntries([...design.notes].filter(([boardId,list])=>list.length>0&&boards.some((b)=>b.id===boardId))),designDoc={formatVersion:DESIGN_BUNDLE_FORMAT,title,viewport,defaultLibrary:config.defaultLibrary,libraries:config.libraries,extensions:config.extensions??{},viewportPresets:config.viewportPresets??[],theme:design.theme,themes:Object.fromEntries(design.themes),customCss:design.customCss,snippets:host.snippets,screens:host.screens,boards,annotations,notes,live,snapshotCssPath:"snapshot.css",...live?{bundlePath:"bundle.js"}:{},...host.stylesheets.length>0?{hostStylesheets:host.stylesheets}:{},...shots?{screenshots:shots.manifest}:{}},validated=DesignBundleSchema.safeParse(designDoc);if(!validated.success)return err(bundleInvalid(prettifyError(validated.error)));addFile("design.json",JSON.stringify(designDoc),"application/json"),addFile("snapshot.css",snapshotCss||`/* this folder uses no CSS framework */
|
|
4
|
+
`,"text/css");let assetRefs=new Set,assetRe=/\/assets\/[A-Za-z0-9._@\-/]+/g;for(let source of[...host.screens,...host.snippets])for(let m of JSON.stringify(source).matchAll(assetRe))assetRefs.add(m[0]);for(let ref of assetRefs){if(ref.startsWith("/assets/host/"))continue;let rel=ref.replace(/^\//,"");try{let bytes=await readFile(join(root,rel));addFile(rel,/\.svg$/i.test(rel)?Buffer.from(sanitizeSvgMarkup(bytes.toString("utf8")),"utf8"):bytes)}catch{report({kind:"warn",message:`asset not found: ${rel}`})}}let commitSha=gitCommitSha(root);if(commitSha)form.append("commitSha",commitSha);let git=request.provenance??gitContext(root);if(reportProvenance(git,report),git.repo)form.append("gitRepo",git.repo);if(git.branch)form.append("gitBranch",git.branch);let folderId=await ensureFolderId(root,config.folderId,report);report({kind:"step",step:"upload",message:`uploading ${bundleFiles} file${bundleFiles===1?"":"s"} (${formatBytes(bundleBytes)})`});let uploaded=await uploadLinkBundle({baseUrl:cloud.baseUrl,token:cloud.token,link:{...request.destination.slug?{slug:request.destination.slug}:{},folderId,publishMode:request.destination.mode,...request.destination.mode==="update"?{expectedVersionId:request.destination.expectedVersionId}:{},title,visibility:request.visibility,...request.teamId?{teamId:request.teamId}:{},...request.audience?.length?{audience:request.audience}:{},...request.publicComments!==void 0?{publicComments:request.publicComments}:{},...request.password?{password:request.password}:{},...request.passwordExpiresAt?{passwordExpiresAt:request.passwordExpiresAt}:{}},form});if(!uploaded.ok)return uploaded;let upload=uploaded.value;return ok({shareUrl:upload.shareUrl,slug:upload.link.slug,visibility:upload.link.visibility,passwordProtected:upload.link.passwordProtected,...upload.link.audience!==void 0?{audience:upload.link.audience}:{},...upload.link.publicComments!==void 0?{publicComments:upload.link.publicComments}:{},files:upload.files,bytes:upload.bytes,screenshots:shots?.files.length??0,boards:boards.length,screens:screens.length,commitSha,created:upload.created,...upload.tier!==void 0?{tier:upload.tier}:{},...upload.history!==void 0?{history:upload.history}:{}})}function formatBytes(bytes){if(bytes>=1048576)return`${(bytes/1048576).toFixed(1)} MB`;if(bytes>=1024)return`${Math.round(bytes/1024)} KB`;return`${bytes} B`}function createPublishBundler(root,config){return new LiveBundler(root,()=>config,()=>liveExtensions(config.extensions),!0,!0)}function publishedDesignSubtitle(design){let published=design.lastPublishedAt?`Published ${design.lastPublishedAt}`:"Publish time unavailable",who=design.ownerEmail?.trim()||design.ownerName?.trim(),publisher=who?`by ${who}`:"publisher unavailable",repo=design.git?.repo?.trim(),branch=design.git?.branch?.trim(),source=repo?`repo ${repo}${branch?` (${branch})`:""}`:"repository unavailable";return`${published} \xB7 ${publisher} \xB7 ${source}`}async function listPublishedDesigns(opts){let body=await cloudFetch(`${opts.baseUrl}/v1/links`,PublishedDesignsResponseSchema,{operation:"listing published designs",token:opts.token});if(!body.ok)return body;return ok((body.value.links??[]).filter((link)=>link.published).map((link)=>({...link,url:link.url.startsWith("http")?link.url:`${opts.baseUrl}${link.url}`})))}async function unpublishDesign(opts){let res=await fetch(`${opts.baseUrl}/v1/links/${encodeURIComponent(opts.slug)}`,{method:"DELETE",headers:{authorization:`Bearer ${opts.token}`}}).catch((error)=>error);if(!(res instanceof Response))return err(unreachable(res,{url:opts.baseUrl}));if(!res.ok)return err(await httpFailureFrom("unpublishing the design",res));return ok(void 0)}
|
|
5
|
+
export{publishedDesignSubtitle,listPublishedDesigns,unpublishDesign,listPublishDestinations,designGitEnv,teamChoiceRequired,describePublishError,recommendedPublishSlot,exactPublishSlots,PUBLISH_VIEWPORT,gitContext,publishableTeams,resolvePublishTeam,listTeams,publishDesign,createPublishBundler};
|