@devosurf/vynt 0.1.2
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 +287 -0
- package/bin/vynt +17 -0
- package/package.json +46 -0
- package/src/bridge.ts +858 -0
- package/src/cli.ts +1449 -0
- package/src/state.ts +167 -0
- package/src/switch.ts +338 -0
- package/src/types.ts +48 -0
- package/vite/index.d.ts +11 -0
- package/vite/index.js +140 -0
- package/web/react/VyntToolbarProvider.d.ts +9 -0
- package/web/react/VyntToolbarProvider.js +615 -0
- package/web/react/index.d.ts +1 -0
- package/web/react/index.js +1 -0
- package/web/vynt-toolbar.js +1075 -0
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# vynt
|
|
2
|
+
|
|
3
|
+
vynt is a local utility for fast UI iteration with parallel agent variants in a single active workspace.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
- Run multiple implementation paths in parallel.
|
|
8
|
+
- Keep one active workspace and one dev server.
|
|
9
|
+
- Compare variants quickly and choose a winner.
|
|
10
|
+
- Keep rollback deterministic.
|
|
11
|
+
|
|
12
|
+
## Current Scope
|
|
13
|
+
|
|
14
|
+
This scaffold provides:
|
|
15
|
+
|
|
16
|
+
- Variant metadata storage at `.vynt/state.json`
|
|
17
|
+
- Objective + variant hierarchy
|
|
18
|
+
- Profile composition with one variant per objective
|
|
19
|
+
- Conflict detection for composed profile selections
|
|
20
|
+
- Workspace apply engine that restores the pinned base and applies selected patch artifacts
|
|
21
|
+
- Docs for MVP architecture and phase plan
|
|
22
|
+
|
|
23
|
+
This scaffold does not yet capture screenshots automatically.
|
|
24
|
+
|
|
25
|
+
## Recommended Core Workflow (Now)
|
|
26
|
+
|
|
27
|
+
Use `vynt` as a standalone CLI while you review UI manually in your browser.
|
|
28
|
+
|
|
29
|
+
1. Start your web app once and keep it running.
|
|
30
|
+
2. Register variants with `add` under each objective.
|
|
31
|
+
3. Switch quickly with `apply` (single variant) or `profile apply` (cross-objective composition).
|
|
32
|
+
4. Refresh the browser and review the result manually.
|
|
33
|
+
5. Finalize objective winners after visual review.
|
|
34
|
+
|
|
35
|
+
This keeps the core loop simple: deterministic patch switching plus manual UI verification.
|
|
36
|
+
|
|
37
|
+
## Browser Toolbar Devtool
|
|
38
|
+
|
|
39
|
+
You can run a local bridge and control variant switching directly in the browser.
|
|
40
|
+
|
|
41
|
+
1. Use `vynt/vite` plugin in your Vite config (recommended) so bridge starts automatically.
|
|
42
|
+
2. Mount the React provider (recommended) or inject `toolbar.js` for non-React pages.
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<script src="http://127.0.0.1:4173/toolbar.js" data-vynt-bridge="http://127.0.0.1:4173"></script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Toolbar capabilities in this MVP:
|
|
49
|
+
|
|
50
|
+
- Objective + variant selection
|
|
51
|
+
- Previous/next variant stepping with index counter (`x/y`)
|
|
52
|
+
- Single-variant apply
|
|
53
|
+
- Profile apply
|
|
54
|
+
- Rollback to latest snapshot
|
|
55
|
+
|
|
56
|
+
React/Next helper component (one-line mount, no script fetch required):
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// app/layout.tsx or root provider
|
|
60
|
+
import { VyntToolbarProvider } from "vynt/web/react/index.js"
|
|
61
|
+
|
|
62
|
+
export function DevTools() {
|
|
63
|
+
return <VyntToolbarProvider />
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The React provider renders the toolbar inline and talks directly to the bridge API (`/status`, `/apply`, `/rollback`, `/events`). It does not load `toolbar.js`.
|
|
68
|
+
With `vynt/vite` plugin, provider default bridge URL is same-origin `"/__vynt"`.
|
|
69
|
+
|
|
70
|
+
Vite auto-bridge setup (no separate `vynt bridge serve` command):
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { defineConfig } from "vite"
|
|
74
|
+
import react from "@vitejs/plugin-react"
|
|
75
|
+
import { vyntVitePlugin } from "vynt/vite"
|
|
76
|
+
|
|
77
|
+
export default defineConfig({
|
|
78
|
+
plugins: [react(), vyntVitePlugin()],
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Optional plugin config:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
vyntVitePlugin({
|
|
86
|
+
bridgeHost: "127.0.0.1",
|
|
87
|
+
bridgePort: 4173,
|
|
88
|
+
prefix: "/__vynt",
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Manual script injection is still available when you are not using React:
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<script src="http://127.0.0.1:4173/toolbar.js" data-vynt-bridge="http://127.0.0.1:4173"></script>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Custom bridge URL:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<VyntToolbarProvider bridgeUrl="http://127.0.0.1:4173" />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use custom `bridgeUrl` when you do not use the Vite plugin proxy.
|
|
105
|
+
|
|
106
|
+
## Test in a Real Project
|
|
107
|
+
|
|
108
|
+
Yes, you can test this today with the same link workflow.
|
|
109
|
+
|
|
110
|
+
1. In this repo: `bun link`
|
|
111
|
+
2. In your target web project: `bun link vynt`
|
|
112
|
+
3. In target project root: `vynt init "$(git rev-parse HEAD)"` (if not already initialized)
|
|
113
|
+
4. Add `vyntVitePlugin()` to your Vite config
|
|
114
|
+
5. Mount the provider in your app:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { VyntToolbarProvider } from "vynt/web/react/index.js"
|
|
118
|
+
|
|
119
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
120
|
+
return (
|
|
121
|
+
<html>
|
|
122
|
+
<body>
|
|
123
|
+
<VyntToolbarProvider />
|
|
124
|
+
{children}
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
6. Open the app in browser; toolbar appears bottom-right.
|
|
132
|
+
7. Use objective/variant selectors or previous/next stepping to switch quickly.
|
|
133
|
+
|
|
134
|
+
## tmux-First Example
|
|
135
|
+
|
|
136
|
+
The project can be operated in tmux without requiring any plugin.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# start a tmux layout (server + live vynt status + operator pane)
|
|
140
|
+
scripts/vynt-tmux.sh start vynt-lab "npm run dev:web"
|
|
141
|
+
|
|
142
|
+
# quick objective/variant apply
|
|
143
|
+
scripts/vynt-tmux.sh apply hero <variant-id>
|
|
144
|
+
|
|
145
|
+
# quick profile apply
|
|
146
|
+
scripts/vynt-tmux.sh profile-apply <profile-id>
|
|
147
|
+
|
|
148
|
+
# interactive selector (uses fzf if installed)
|
|
149
|
+
scripts/vynt-tmux.sh selector
|
|
150
|
+
scripts/vynt-tmux.sh selector profile
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## OpenCode Integration: Standalone First, Plugin Later
|
|
154
|
+
|
|
155
|
+
- Current recommendation: run `vynt` as a standalone CLI from terminal/tmux.
|
|
156
|
+
- Future optional path: an OpenCode plugin hook that auto-registers variants from session lifecycle events.
|
|
157
|
+
- Rationale: keep core switching reliable and tool-agnostic first; add automation hooks when the workflow stabilizes.
|
|
158
|
+
|
|
159
|
+
## Future Optional Automation
|
|
160
|
+
|
|
161
|
+
- `capture`: auto-screenshot routes per variant/profile using Playwright.
|
|
162
|
+
- `compare`: side-by-side or grouped screenshot review.
|
|
163
|
+
- Auth-protected pages can be supported by persisted Playwright auth state (cookies/local storage), so screenshots remain automated.
|
|
164
|
+
|
|
165
|
+
## Quick Start
|
|
166
|
+
|
|
167
|
+
1. `npm install`
|
|
168
|
+
2. `npm run dev -- init <base-ref>`
|
|
169
|
+
3. `npm run dev -- objective create "landing hero redesign" --id x`
|
|
170
|
+
4. `npm run dev -- add x "bold hero" ./patches/x-v2.patch --files=src/app.tsx,src/hero.tsx`
|
|
171
|
+
5. `npm run dev -- profile create "design-a"`
|
|
172
|
+
6. `npm run dev -- profile set design-a x <variant-id>`
|
|
173
|
+
7. `npm run dev -- profile apply design-a`
|
|
174
|
+
8. `npm run dev -- status`
|
|
175
|
+
9. `npm run dev -- list`
|
|
176
|
+
|
|
177
|
+
## Use in Real Projects (bun link)
|
|
178
|
+
|
|
179
|
+
1. In this repo: `bun link`
|
|
180
|
+
2. Ensure Bun bin path is on your shell PATH (usually `~/.bun/bin`).
|
|
181
|
+
3. In any target project: `bun link vynt`
|
|
182
|
+
4. Run from the target project: `vynt --help`
|
|
183
|
+
|
|
184
|
+
Then use the same commands directly (without `npm run dev --`), for example:
|
|
185
|
+
|
|
186
|
+
- `vynt init "$(git rev-parse HEAD)"`
|
|
187
|
+
- `vynt objective create "hero exploration"`
|
|
188
|
+
- `vynt status`
|
|
189
|
+
|
|
190
|
+
## Command Overview
|
|
191
|
+
|
|
192
|
+
All commands are invoked through `vynt`.
|
|
193
|
+
|
|
194
|
+
- `vynt init <base-ref>`
|
|
195
|
+
- `vynt objective create <name> [--id <id>]`
|
|
196
|
+
- `vynt objective list [--json]`
|
|
197
|
+
- `vynt objective finalize <objective-id> <variant-id>`
|
|
198
|
+
- `vynt add <objective-id> <name> <patch-file> [--files=a,b,c] [--session=<id>] [--notes=<text>]`
|
|
199
|
+
- `vynt activate <objective-id> <variant-id>`
|
|
200
|
+
- `vynt apply [<variant-id>] [--objective=<objective-id> --variant=<variant-id>] [--review]`
|
|
201
|
+
- `vynt rollback [<snapshot-id>]`
|
|
202
|
+
- `vynt bridge serve [--host <host>] [--port <port>]`
|
|
203
|
+
- `vynt status [--json]`
|
|
204
|
+
- `vynt opencode register <objective-id> <session-id> <name> <patch-file> [--files=a,b,c] [--notes=<text>]`
|
|
205
|
+
- `vynt opencode register-auto <session-id> <name> <patch-file> [--objective=<objective-id>] [--files=a,b,c] [--notes=<text>]`
|
|
206
|
+
- `vynt opencode capture <session-id> <name> [--objective=<objective-id>] [--notes=<text>] [--reset]`
|
|
207
|
+
- `vynt profile create <name> [--id <id>]`
|
|
208
|
+
- `vynt profile list [--json]`
|
|
209
|
+
- `vynt profile set <profile-id> <objective-id> <variant-id>`
|
|
210
|
+
- `vynt profile clear <profile-id> <objective-id>`
|
|
211
|
+
- `vynt profile apply <profile-id>`
|
|
212
|
+
- `vynt list [--json]`
|
|
213
|
+
|
|
214
|
+
## Helper Scripts
|
|
215
|
+
|
|
216
|
+
- `scripts/vynt-tmux.sh`: tmux-first helper for start/apply/profile-apply/selector flows, including profile selector mode.
|
|
217
|
+
- `scripts/vynt-opencode-hook.sh`: standalone OpenCode hook scaffold for registering session output with direct args or environment variables.
|
|
218
|
+
- `scripts/vynt-variants-parallel.sh`: deterministic backend helper for `/variants` orchestration (`setup`, `wait`, `finalize`, `cleanup`) with patch-based `register-auto`; defaults to `sandbox` backend and supports `worktree` fallback.
|
|
219
|
+
|
|
220
|
+
## Variant Generation Command
|
|
221
|
+
|
|
222
|
+
Use OpenCode custom command `/variants <objective> [count]` (configured in `~/.config/opencode/opencode.jsonc`) to orchestrate multi-variant generation with subagent exploration and `vynt` registration.
|
|
223
|
+
|
|
224
|
+
Variant contract for generated UI patches:
|
|
225
|
+
|
|
226
|
+
- If a variant changes UI markup (`.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`, `.html`) for an objective, include `data-vynt-objective="<objective-id>"` on the objective wrapper container.
|
|
227
|
+
- `vynt opencode register-auto` now enforces this for UI patches and rejects registration when the marker is missing.
|
|
228
|
+
- Subagents should preserve existing `data-vynt-*` attributes and never remove objective markers.
|
|
229
|
+
|
|
230
|
+
`vynt opencode capture` exists for explicit in-session registration of the current diff, including optional `--reset` back to base for the next variant iteration.
|
|
231
|
+
|
|
232
|
+
## OpenCode Hook Scaffold (Standalone)
|
|
233
|
+
|
|
234
|
+
Use the helper script when you want a lightweight plugin-compatible registration path without hard-coupling to any specific OpenCode runtime internals.
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# direct mode
|
|
238
|
+
scripts/vynt-opencode-hook.sh register hero ses_123 "hero bold" ./patches/hero.patch --files src/hero.tsx
|
|
239
|
+
|
|
240
|
+
# env mode (for hooks)
|
|
241
|
+
scripts/vynt-opencode-hook.sh env-template
|
|
242
|
+
scripts/vynt-opencode-hook.sh register-env
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## OpenCode Auto-Register Plugin Setup
|
|
246
|
+
|
|
247
|
+
Auto-registration is optional and runs through an OpenCode plugin event hook.
|
|
248
|
+
|
|
249
|
+
1. Ensure global plugin file exists at `~/.config/opencode/plugin/vynt-autoregister.ts`.
|
|
250
|
+
2. Restart OpenCode so the plugin is loaded.
|
|
251
|
+
|
|
252
|
+
No config is required for objective routing.
|
|
253
|
+
|
|
254
|
+
Optional override file:
|
|
255
|
+
|
|
256
|
+
```json
|
|
257
|
+
{
|
|
258
|
+
"objectiveId": "hero",
|
|
259
|
+
"namePrefix": "agent",
|
|
260
|
+
"enabled": true
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Save this as `.vynt/opencode-autoregister.json` in your project root.
|
|
265
|
+
|
|
266
|
+
Notes:
|
|
267
|
+
- Auto-register now performs objective routing automatically.
|
|
268
|
+
- If one objective matches changed files strongly, it is reused.
|
|
269
|
+
- If routing is ambiguous, a new objective is auto-created from file/name hints.
|
|
270
|
+
- Optional: set `objectiveId` only when you want to force all auto-registered variants into one objective.
|
|
271
|
+
- You can override objective and title prefix through env vars:
|
|
272
|
+
- `VYNT_AUTO_OBJECTIVE_ID`
|
|
273
|
+
- `VYNT_AUTO_NAME_PREFIX`
|
|
274
|
+
- On each `session.idle`, the plugin writes a patch to `.vynt/patches/` and registers it through `vynt opencode register-auto`.
|
|
275
|
+
|
|
276
|
+
## Layout
|
|
277
|
+
|
|
278
|
+
- `src/cli.ts`: command entrypoint
|
|
279
|
+
- `src/bridge.ts`: local HTTP/SSE bridge for browser devtool controls
|
|
280
|
+
- `src/state.ts`: state load/save logic
|
|
281
|
+
- `src/types.ts`: variant model types
|
|
282
|
+
- `web/vynt-toolbar.js`: injected browser toolbar client
|
|
283
|
+
- `web/react/VyntToolbarProvider.js`: React helper for script injection
|
|
284
|
+
- `web/react/index.js`: tiny React barrel export for provider import
|
|
285
|
+
- `docs/mvp-spec.md`: product spec
|
|
286
|
+
- `docs/architecture.md`: technical architecture
|
|
287
|
+
- `docs/implementation-plan.md`: phased execution plan
|
package/bin/vynt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SOURCE_PATH="${BASH_SOURCE[0]}"
|
|
5
|
+
|
|
6
|
+
while [ -L "$SOURCE_PATH" ]; do
|
|
7
|
+
SOURCE_DIR="$(cd -P "$(dirname "$SOURCE_PATH")" >/dev/null 2>&1 && pwd)"
|
|
8
|
+
LINK_TARGET="$(readlink "$SOURCE_PATH")"
|
|
9
|
+
if [[ "$LINK_TARGET" = /* ]]; then
|
|
10
|
+
SOURCE_PATH="$LINK_TARGET"
|
|
11
|
+
else
|
|
12
|
+
SOURCE_PATH="$SOURCE_DIR/$LINK_TARGET"
|
|
13
|
+
fi
|
|
14
|
+
done
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE_PATH")" >/dev/null 2>&1 && pwd)"
|
|
17
|
+
exec bun "$SCRIPT_DIR/../src/cli.ts" "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devosurf/vynt",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"bin",
|
|
7
|
+
"src",
|
|
8
|
+
"vite",
|
|
9
|
+
"web"
|
|
10
|
+
],
|
|
11
|
+
"bin": {
|
|
12
|
+
"vynt": "./bin/vynt"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/cli.ts",
|
|
16
|
+
"./web/react": {
|
|
17
|
+
"types": "./web/react/index.d.ts",
|
|
18
|
+
"default": "./web/react/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./web/react/index.js": {
|
|
21
|
+
"types": "./web/react/index.d.ts",
|
|
22
|
+
"default": "./web/react/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./vite": {
|
|
25
|
+
"types": "./vite/index.d.ts",
|
|
26
|
+
"default": "./vite/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./vite/index.js": {
|
|
29
|
+
"types": "./vite/index.d.ts",
|
|
30
|
+
"default": "./vite/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "tsx src/cli.ts",
|
|
35
|
+
"test": "tsx --test tests/**/*.test.ts",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "^14.0.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.13.10",
|
|
43
|
+
"tsx": "^4.19.2",
|
|
44
|
+
"typescript": "^5.8.2"
|
|
45
|
+
}
|
|
46
|
+
}
|