@devosurf/vynt
v0.1.6
Published
vynt is a local utility for fast UI iteration with parallel agent variants in a single active workspace.
Downloads
512
Readme
vynt
vynt is a local utility for fast UI iteration with parallel agent variants in a single active workspace.
Why
- Run multiple implementation paths in parallel.
- Keep one active workspace and one dev server.
- Compare variants quickly and choose a winner.
- Keep rollback deterministic.
Current Scope
This scaffold provides:
- Variant metadata storage at
.vynt/state.json - Objective + variant hierarchy
- Profile composition with one variant per objective
- Conflict detection for composed profile selections
- Workspace apply engine that restores the pinned base and applies selected patch artifacts
- Docs for MVP architecture and phase plan
This scaffold does not yet capture screenshots automatically.
Recommended Core Workflow (Now)
Use vynt as a standalone CLI while you review UI manually in your browser.
- Start your web app once and keep it running.
- Register variants with
addunder each objective. - Switch quickly with
apply(single variant) orprofile apply(cross-objective composition). - Refresh the browser and review the result manually.
- Finalize objective winners after visual review.
This keeps the core loop simple: deterministic patch switching plus manual UI verification.
Browser Toolbar Devtool
You can run a local bridge and control variant switching directly in the browser.
- Use
vynt/viteplugin in your Vite config (recommended) so bridge starts automatically. - Mount the React provider (recommended) or inject
toolbar.jsfor non-React pages.
<script src="http://127.0.0.1:4173/toolbar.js" data-vynt-bridge="http://127.0.0.1:4173"></script>Toolbar capabilities in this MVP:
- Objective + variant selection
- Previous/next variant stepping with index counter (
x/y) - Single-variant apply
- Profile apply
- Rollback to latest snapshot
React/Next helper component (one-line mount, no script fetch required):
// app/layout.tsx or root provider
import { VyntToolbarProvider } from "vynt/web/react/index.js"
export function DevTools() {
return <VyntToolbarProvider />
}The React provider renders the toolbar inline and talks directly to the bridge API (/status, /apply, /rollback, /events). It does not load toolbar.js.
With vynt/vite plugin, provider default bridge URL is same-origin "/__vynt".
Vite auto-bridge setup (no separate vynt bridge serve command):
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { vyntVitePlugin } from "vynt/vite"
export default defineConfig({
plugins: [react(), vyntVitePlugin()],
})Optional plugin config:
vyntVitePlugin({
bridgeHost: "127.0.0.1",
bridgePort: 4173,
prefix: "/__vynt",
})Manual script injection is still available when you are not using React:
<script src="http://127.0.0.1:4173/toolbar.js" data-vynt-bridge="http://127.0.0.1:4173"></script>Custom bridge URL:
<VyntToolbarProvider bridgeUrl="http://127.0.0.1:4173" />Use custom bridgeUrl when you do not use the Vite plugin proxy.
Test in a Real Project
Yes, you can test this today with the same link workflow.
- In this repo:
bun link - In your target web project:
bun link vynt - In target project root:
vynt init "$(git rev-parse HEAD)"(if not already initialized) - Add
vyntVitePlugin()to your Vite config - Mount the provider in your app:
import { VyntToolbarProvider } from "vynt/web/react/index.js"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<VyntToolbarProvider />
{children}
</body>
</html>
)
}- Open the app in browser; toolbar appears bottom-right.
- Use objective/variant selectors or previous/next stepping to switch quickly.
tmux-First Example
The project can be operated in tmux without requiring any plugin.
# start a tmux layout (server + live vynt status + operator pane)
scripts/vynt-tmux.sh start vynt-lab "npm run dev:web"
# quick objective/variant apply
scripts/vynt-tmux.sh apply hero <variant-id>
# quick profile apply
scripts/vynt-tmux.sh profile-apply <profile-id>
# interactive selector (uses fzf if installed)
scripts/vynt-tmux.sh selector
scripts/vynt-tmux.sh selector profileOpenCode Integration: Standalone First, Plugin Later
- Current recommendation: run
vyntas a standalone CLI from terminal/tmux. - Future optional path: an OpenCode plugin hook that auto-registers variants from session lifecycle events.
- Rationale: keep core switching reliable and tool-agnostic first; add automation hooks when the workflow stabilizes.
Future Optional Automation
capture: auto-screenshot routes per variant/profile using Playwright.compare: side-by-side or grouped screenshot review.- Auth-protected pages can be supported by persisted Playwright auth state (cookies/local storage), so screenshots remain automated.
Quick Start
npm installnpm run dev -- init <base-ref>npm run dev -- objective create "landing hero redesign" --id xnpm run dev -- add x "bold hero" ./patches/x-v2.patch --files=src/app.tsx,src/hero.tsxnpm run dev -- profile create "design-a"npm run dev -- profile set design-a x <variant-id>npm run dev -- profile apply design-anpm run dev -- statusnpm run dev -- list
Use in Real Projects (bun link)
- In this repo:
bun link - Ensure Bun bin path is on your shell PATH (usually
~/.bun/bin). - In any target project:
bun link vynt - Run from the target project:
vynt --help
Then use the same commands directly (without npm run dev --), for example:
vynt init "$(git rev-parse HEAD)"vynt objective create "hero exploration"vynt status
Command Overview
All commands are invoked through vynt.
vynt init <base-ref>vynt objective create <name> [--id <id>]vynt objective list [--json]vynt objective finalize <objective-id> <variant-id>vynt add <objective-id> <name> <patch-file> [--files=a,b,c] [--session=<id>] [--notes=<text>]vynt activate <objective-id> <variant-id>vynt apply [<variant-id>] [--objective=<objective-id> --variant=<variant-id>] [--review]vynt rollback [<snapshot-id>]vynt bridge serve [--host <host>] [--port <port>]vynt status [--json]vynt opencode register <objective-id> <session-id> <name> <patch-file> [--files=a,b,c] [--notes=<text>]vynt opencode register-auto <session-id> <name> <patch-file> [--objective=<objective-id>] [--files=a,b,c] [--notes=<text>]vynt opencode capture <session-id> <name> [--objective=<objective-id>] [--notes=<text>] [--reset]vynt profile create <name> [--id <id>]vynt profile list [--json]vynt profile set <profile-id> <objective-id> <variant-id>vynt profile clear <profile-id> <objective-id>vynt profile apply <profile-id>vynt list [--json]
Helper Scripts
scripts/vynt-tmux.sh: tmux-first helper for start/apply/profile-apply/selector flows, including profile selector mode.scripts/vynt-opencode-hook.sh: standalone OpenCode hook scaffold for registering session output with direct args or environment variables.scripts/vynt-variants-parallel.sh: deterministic backend helper for/variantsorchestration (setup,wait,finalize,cleanup) with patch-basedregister-auto; defaults tosandboxbackend and supportsworktreefallback.
Variant Generation Command
Use OpenCode custom command /variants <objective> [count] (configured in ~/.config/opencode/opencode.jsonc) to orchestrate multi-variant generation with subagent exploration and vynt registration.
Variant contract for generated UI patches:
- If a variant changes UI markup (
.tsx,.jsx,.vue,.svelte,.astro,.html) for an objective, includedata-vynt-objective="<objective-id>"on the objective wrapper container. vynt opencode register-autonow enforces this for UI patches and rejects registration when the marker is missing.- Subagents should preserve existing
data-vynt-*attributes and never remove objective markers.
vynt opencode capture exists for explicit in-session registration of the current diff, including optional --reset back to base for the next variant iteration.
OpenCode Hook Scaffold (Standalone)
Use the helper script when you want a lightweight plugin-compatible registration path without hard-coupling to any specific OpenCode runtime internals.
# direct mode
scripts/vynt-opencode-hook.sh register hero ses_123 "hero bold" ./patches/hero.patch --files src/hero.tsx
# env mode (for hooks)
scripts/vynt-opencode-hook.sh env-template
scripts/vynt-opencode-hook.sh register-envOpenCode Auto-Register Plugin Setup
Auto-registration is optional and runs through an OpenCode plugin event hook.
- Ensure global plugin file exists at
~/.config/opencode/plugin/vynt-autoregister.ts. - Restart OpenCode so the plugin is loaded.
No config is required for objective routing.
Optional override file:
{
"objectiveId": "hero",
"namePrefix": "agent",
"enabled": true
}Save this as .vynt/opencode-autoregister.json in your project root.
Notes:
- Auto-register now performs objective routing automatically.
- If one objective matches changed files strongly, it is reused.
- If routing is ambiguous, a new objective is auto-created from file/name hints.
- Optional: set
objectiveIdonly when you want to force all auto-registered variants into one objective. - You can override objective and title prefix through env vars:
VYNT_AUTO_OBJECTIVE_IDVYNT_AUTO_NAME_PREFIX
- On each
session.idle, the plugin writes a patch to.vynt/patches/and registers it throughvynt opencode register-auto.
Layout
src/cli.ts: command entrypointsrc/bridge.ts: local HTTP/SSE bridge for browser devtool controlssrc/state.ts: state load/save logicsrc/types.ts: variant model typesweb/vynt-toolbar.js: injected browser toolbar clientweb/react/VyntToolbarProvider.js: React helper for script injectionweb/react/index.js: tiny React barrel export for provider importdocs/mvp-spec.md: product specdocs/architecture.md: technical architecturedocs/implementation-plan.md: phased execution plan
