@lalalic/markcut 3.1.1 → 3.2.1
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/docs/research/2026-09-19-chatgpt-browser-vision.md +250 -0
- package/package.json +5 -1
- package/skills/markcut/SKILL.md +14 -17
- package/skills/markcut/docs/markdown-descriptive.md +1 -1
- package/skills/markcut/review.md +480 -0
- package/tests/evals/README.md +41 -0
- package/tests/evals/dataset.json +170 -0
- package/tests/evals/gen_dataset.py +63 -0
- package/tests/evals/metrics.py +70 -0
- package/tests/evals/openrouter_model.py +143 -0
- package/tests/evals/storyboard_app.py +55 -0
- package/tests/evals/test_storyboard.py +32 -0
- package/.vscode/settings.json +0 -3
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# ChatGPT Browser Harness as a Markcut Vision Backend
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-19
|
|
4
|
+
PR: #3
|
|
5
|
+
Recommendation: **not currently viable**
|
|
6
|
+
|
|
7
|
+
## Objective
|
|
8
|
+
|
|
9
|
+
Evaluate whether Markcut's configurable Vision CLI can use an authenticated ChatGPT web session, operated through the `chatgpt-browser-worker` skill and `browser-harness`, as an image/video understanding backend without using the ChatGPT API or the legacy MacDeveloperBridge Chrome/token path.
|
|
10
|
+
|
|
11
|
+
## Existing Markcut seam
|
|
12
|
+
|
|
13
|
+
Markcut already has a narrow backend seam in `src/config.mjs`:
|
|
14
|
+
|
|
15
|
+
- `DEFAULT_ITT_CLI` for image-to-text
|
|
16
|
+
- `DEFAULT_VTT_CLI` for video-to-text
|
|
17
|
+
- `DEFAULT_AGENT_CLI` for downstream agent work
|
|
18
|
+
|
|
19
|
+
The Vision CLI substitutes `{prompt}` and `{input}` into these command templates, so a future browser-backed backend does not require rewriting the Vision pipeline. The correct integration shape would be a small executable command that accepts the same prompt/input contract and emits normalized text/JSON on stdout.
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
M[markcut vision] --> C[configured ITT/VTT command]
|
|
24
|
+
C -->|current| L[mlx-vlm]
|
|
25
|
+
C -->|experimental| B[chatgpt-browser-worker adapter]
|
|
26
|
+
B --> H[browser-harness]
|
|
27
|
+
H --> W[authenticated ChatGPT web UI]
|
|
28
|
+
W --> R[verified assistant result]
|
|
29
|
+
R --> N[normalize JSON/text]
|
|
30
|
+
N --> M
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Browser-worker path tested
|
|
34
|
+
|
|
35
|
+
The experiment used the `chatgpt-browser-worker` contract from the Agents Relay worktree. The worker uses `browser-harness` as the only browser interaction layer and preserves ChatGPT thread identity and Project identity.
|
|
36
|
+
|
|
37
|
+
Observed worker thread:
|
|
38
|
+
|
|
39
|
+
- Project: `agents-relay`
|
|
40
|
+
- Thread: `6aaf32d4-4164-83ea-860a-c2453ce57e51`
|
|
41
|
+
- Requested thinking: `high`
|
|
42
|
+
- Effective thinking: `unknown` (UI could not verify it; no inference was made)
|
|
43
|
+
|
|
44
|
+
A second experiment targeted the existing `markcut` ChatGPT Project. The helper selected the Project, but current ChatGPT UI drift prevented its create helper from reliably observing/sending the composer without manual self-healing.
|
|
45
|
+
|
|
46
|
+
## Synthetic fixtures
|
|
47
|
+
|
|
48
|
+
To avoid private media and make correctness objectively measurable, the test generated:
|
|
49
|
+
|
|
50
|
+
1. `image-test-unique.png`
|
|
51
|
+
- red rectangle
|
|
52
|
+
- blue circle
|
|
53
|
+
- green triangle
|
|
54
|
+
- visible text identifying all three shapes and a unique fixture label
|
|
55
|
+
2. `video-test.mp4`
|
|
56
|
+
- 6 seconds total
|
|
57
|
+
- 0-2s red phase
|
|
58
|
+
- 2-4s blue phase
|
|
59
|
+
- 4-6s green phase
|
|
60
|
+
|
|
61
|
+
Fixtures were generated locally and kept out of Git.
|
|
62
|
+
|
|
63
|
+
## Control: existing Markcut image backend
|
|
64
|
+
|
|
65
|
+
The current configured image backend was run directly with the same image fixture using the existing `mlx-vlm`/MiniCPM path.
|
|
66
|
+
|
|
67
|
+
Observed runtime: approximately 8 seconds.
|
|
68
|
+
|
|
69
|
+
Observed result correctly identified:
|
|
70
|
+
|
|
71
|
+
- red rectangle
|
|
72
|
+
- blue circle
|
|
73
|
+
- green triangle
|
|
74
|
+
- `RED RECTANGLE | BLUE CIRCLE | GREEN TRIANGLE`
|
|
75
|
+
- `MARKCUT VISION UNIQUE FIXTURE 2026-09-19`
|
|
76
|
+
|
|
77
|
+
This establishes that the fixture and current Markcut backend are functioning.
|
|
78
|
+
|
|
79
|
+
## ChatGPT image experiment
|
|
80
|
+
|
|
81
|
+
The Browser Harness path successfully:
|
|
82
|
+
|
|
83
|
+
- selected an authenticated ChatGPT Project;
|
|
84
|
+
- created and preserved a real ChatGPT thread id;
|
|
85
|
+
- uploaded an image into the composer;
|
|
86
|
+
- submitted a media-analysis prompt through the browser UI;
|
|
87
|
+
- observed assistant message ids from the actual ChatGPT DOM.
|
|
88
|
+
|
|
89
|
+
However, the result path was not reliable enough for a backend:
|
|
90
|
+
|
|
91
|
+
1. One submitted image turn produced a terminal-looking but truncated assistant message:
|
|
92
|
+
|
|
93
|
+
`{"description":"A`
|
|
94
|
+
|
|
95
|
+
2. The existing `result` helper incorrectly considered that partial text stable/final because its generating-state detector did not match the current UI state.
|
|
96
|
+
3. A later image retry entered a persistent `Stop answering` state while the attachment and prompt remained in the composer and no durable new user turn was observed.
|
|
97
|
+
4. The current helper's composer logic also drifted from the current ChatGPT DOM. In the current UI, the visible composer is a `contenteditable` element while a separate textarea may coexist. The helper can fill the wrong semantic surface or fail to observe the send control.
|
|
98
|
+
5. Direct self-healing through fresh DOM observation could send ordinary text turns, but media-turn behavior was still inconsistent.
|
|
99
|
+
|
|
100
|
+
### Structured-output reliability
|
|
101
|
+
|
|
102
|
+
Result: **fail** for backend-grade use.
|
|
103
|
+
|
|
104
|
+
A Markcut backend needs deterministic success/failure signaling and complete output. A truncated JSON prefix that is misclassified as complete is unsafe because downstream parsing or cache persistence can accept corrupted evidence.
|
|
105
|
+
|
|
106
|
+
## Video experiment
|
|
107
|
+
|
|
108
|
+
Direct video upload was attempted as the preferred path. The experiment reached a second failure mode before a trustworthy model result could be obtained: after opening the `markcut` Project for a fresh direct-video test, the current helper/UI combination failed to observe the expected composer surface consistently.
|
|
109
|
+
|
|
110
|
+
Because image media submission was already non-deterministic and the direct-video setup could not produce a verified, repeatable turn, the experiment did **not** treat a frame-sampling fallback as proof that direct video upload works.
|
|
111
|
+
|
|
112
|
+
A frame-sampling fallback remains technically possible because Markcut already owns ffmpeg normalization/sampling and can present sampled images to a backend. But the browser-backed image transport itself must first become reliable.
|
|
113
|
+
|
|
114
|
+
```mermaid
|
|
115
|
+
flowchart TD
|
|
116
|
+
V[video input] --> D{direct web video upload reliable?}
|
|
117
|
+
D -->|current result: no| F[Markcut frame sampling]
|
|
118
|
+
F --> I[image batches]
|
|
119
|
+
I --> B[ChatGPT browser backend]
|
|
120
|
+
B --> Q{media turn + result verified?}
|
|
121
|
+
Q -->|current result: no| X[do not enable backend]
|
|
122
|
+
Q -->|future: yes| O[chronology / scene JSON]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Failure modes observed
|
|
126
|
+
|
|
127
|
+
| Area | Observation | Impact |
|
|
128
|
+
| --- | --- | --- |
|
|
129
|
+
| Composer detection | Current UI exposes both contenteditable and textarea-like surfaces | helper may target the wrong composer |
|
|
130
|
+
| Send detection | helper could report no `Send prompt` even when manual re-observation later found a usable control | brittle create/continue lifecycle |
|
|
131
|
+
| Media upload | attachment preview can exist without a reliable submitted turn | no durable proof input reached model |
|
|
132
|
+
| Generating detection | partial result was considered final | corrupted JSON may be accepted |
|
|
133
|
+
| Result verification | stable DOM text alone is insufficient when streaming state is misdetected | false completion |
|
|
134
|
+
| Project page lifecycle | new Project chat can change composer state/URL after first send | helper assumptions stale |
|
|
135
|
+
| Video | direct video path was not repeatably driven to a verified result | no production evidence |
|
|
136
|
+
|
|
137
|
+
## Minimum changes required before re-benchmarking
|
|
138
|
+
|
|
139
|
+
1. **Fix semantic composer selection**
|
|
140
|
+
- prefer the visible `contenteditable` composer by accessibility role/name;
|
|
141
|
+
- reject ambiguous multiple composer surfaces;
|
|
142
|
+
- verify typed text appears in the same visible surface before sending.
|
|
143
|
+
|
|
144
|
+
2. **Fix send verification**
|
|
145
|
+
- re-observe the current send control after typing/upload;
|
|
146
|
+
- after click, require a new user-turn node or other durable conversation evidence;
|
|
147
|
+
- do not return `prompt_sent: true` from a click alone.
|
|
148
|
+
|
|
149
|
+
3. **Fix streaming/final-result verification**
|
|
150
|
+
- do not infer completion only from absence of one historical stop selector;
|
|
151
|
+
- require the assistant message text to remain stable across a longer interval and require the conversation to return to an input-ready state;
|
|
152
|
+
- for structured output, parse the requested JSON before returning success.
|
|
153
|
+
|
|
154
|
+
4. **Add media-specific lifecycle states**
|
|
155
|
+
- upload started
|
|
156
|
+
- attachment observed
|
|
157
|
+
- attachment ready
|
|
158
|
+
- turn submitted
|
|
159
|
+
- assistant started
|
|
160
|
+
- assistant complete
|
|
161
|
+
- output validated
|
|
162
|
+
|
|
163
|
+
5. **Add regression fixtures**
|
|
164
|
+
- one small PNG
|
|
165
|
+
- one short MP4
|
|
166
|
+
- exact expected JSON fields
|
|
167
|
+
- timeout/stuck-generation case
|
|
168
|
+
- truncated-JSON case
|
|
169
|
+
|
|
170
|
+
6. **Only then add a Markcut adapter command**
|
|
171
|
+
- keep it experimental and opt-in;
|
|
172
|
+
- preserve the existing ITT/VTT CLI template boundary;
|
|
173
|
+
- do not change the default backend.
|
|
174
|
+
|
|
175
|
+
## Proposed future adapter shape
|
|
176
|
+
|
|
177
|
+
If the browser worker is repaired, the narrowest Markcut integration remains a command adapter rather than changes throughout the Vision pipeline:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
markcut vision
|
|
181
|
+
-> MARKCUT_ITT_CLI / MARKCUT_VTT_CLI
|
|
182
|
+
-> chatgpt-browser-vision executable
|
|
183
|
+
-> chatgpt-browser-worker typed lifecycle
|
|
184
|
+
-> browser-harness
|
|
185
|
+
-> verified JSON stdout
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The adapter should be responsible for media upload, schema validation, retry/resume of the same ChatGPT thread, and non-zero exit on unverified/truncated output.
|
|
189
|
+
|
|
190
|
+
## Recommendation
|
|
191
|
+
|
|
192
|
+
**Not currently viable.**
|
|
193
|
+
|
|
194
|
+
The architectural seam is good and ChatGPT web Vision is reachable through the intended browser-harness stack, but the current `chatgpt-browser-worker` implementation does not yet provide backend-grade guarantees for media submission or final-result verification. The existing Markcut local Vision backend remains substantially more deterministic for this workflow.
|
|
195
|
+
|
|
196
|
+
Do not switch Markcut's default Vision backend. Repair and regression-test the browser worker first, then rerun this PR's image and video fixtures. Once image media turns are deterministic, evaluate video in this order:
|
|
197
|
+
|
|
198
|
+
1. direct MP4 upload;
|
|
199
|
+
2. Markcut-owned frame sampling if direct upload is unsupported or unstable;
|
|
200
|
+
3. compare output quality, JSON validity, p50/p95 latency, and retry rate against the existing backend.
|
|
201
|
+
|
|
202
|
+
## Commands/evidence
|
|
203
|
+
|
|
204
|
+
Control image command used:
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
uvx --from mlx-vlm mlx_vlm.generate \
|
|
208
|
+
--model mlx-community/MiniCPM-V-4.6-bf16 \
|
|
209
|
+
--max-tokens 512 \
|
|
210
|
+
--prompt 'Describe this image precisely, including colored shapes and visible text. Return concise JSON.' \
|
|
211
|
+
--image .tmp/pr3-vision/image-test-unique.png \
|
|
212
|
+
--temperature 0.0 \
|
|
213
|
+
--thinking-mode disabled
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Browser path used the `chatgpt-browser-worker` skill plus `browser-harness`; no ChatGPT API call, copied cookie, or MacDeveloperBridge ChatGPT runtime was used.
|
|
217
|
+
|
|
218
|
+
## Re-benchmark after browser-worker attachment and tab-lifecycle fixes
|
|
219
|
+
|
|
220
|
+
A second pass was run after Neo main added verified attachment submission (`defe80d`) and operation-owned tab cleanup (`3b4331e`). This changed the result in two useful ways:
|
|
221
|
+
|
|
222
|
+
- attachment submission itself is now materially safer: a prior smoke run observed the requested filename, prompt text, a new durable user turn, and a real user message ID before reporting success;
|
|
223
|
+
- `result --expect-json` now rejects malformed/truncated assistant output instead of treating it as completed. A deliberately observed truncated response failed with `assistant result is not valid JSON`, which closes the false-completion path seen in the first pass;
|
|
224
|
+
- operation-owned tab cleanup was verified by comparing page targets before and after an operation that opened a temporary thread tab; the target sets were identical after return.
|
|
225
|
+
|
|
226
|
+
The re-benchmark also exposed a new blocking failure at the durable-thread boundary. Threads that are visible and usable when reached through the existing ChatGPT sidebar can load as a shell-only page when opened directly by their durable conversation URL in a fresh tab. In the observed failures, the URL and Project sidebar loaded, but the conversation had **0 visible user messages, 0 visible assistant messages, and 0 composer surfaces**. `operate_bh.py` therefore correctly failed with `ChatGPT composer was not observed` rather than sending into an ambiguous page.
|
|
227
|
+
|
|
228
|
+
This matters because the current worker contract reopens a durable thread by URL for `send`, `status`, and `result`. A Vision backend cannot be considered reliable if durable thread recovery depends on whether the ChatGPT SPA was entered through sidebar navigation versus direct URL navigation.
|
|
229
|
+
|
|
230
|
+
### Updated failure boundary
|
|
231
|
+
|
|
232
|
+
```mermaid
|
|
233
|
+
flowchart TD
|
|
234
|
+
A[Markcut media input] --> B[chatgpt-browser-worker send]
|
|
235
|
+
B --> C{attachment + durable user turn verified?}
|
|
236
|
+
C -->|yes| D[assistant generation]
|
|
237
|
+
C -->|no| X[fail safely]
|
|
238
|
+
D --> E{result JSON valid?}
|
|
239
|
+
E -->|no| X
|
|
240
|
+
E -->|yes| F[backend result]
|
|
241
|
+
R[resume/reopen thread by durable URL] --> G{conversation UI + composer restored?}
|
|
242
|
+
G -->|current rerun: not reliably| X
|
|
243
|
+
G -->|future| B
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Updated recommendation
|
|
247
|
+
|
|
248
|
+
The browser-worker changes fix two important defects from the first pass—attachment-submit evidence and structured-result validation—but PR #3 should still remain research-only. The remaining blocker is now narrower and clearer: **reliable durable-thread reopening / SPA recovery**. Until the worker can reopen the exact `thread_id` and observe the same conversation state independent of navigation path, direct image/video benchmarking cannot be considered repeatable enough for a Markcut backend.
|
|
249
|
+
|
|
250
|
+
The next browser-worker fix should therefore target thread reopening semantics: prefer semantic sidebar navigation to the exact observed thread when direct URL hydration yields an empty shell, verify the requested `thread_id` after navigation, and only then expose the composer or result operations. After that, rerun the same PNG and MP4 fixtures without changing Markcut itself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lalalic/markcut",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Markdown-to-video engine. Describe scenes in markdown, get a rendered video.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"markcut": "bin/markcut"
|
|
@@ -65,5 +65,9 @@
|
|
|
65
65
|
"@types/react-dom": "^19.0.0",
|
|
66
66
|
"typescript": "^5.6.0",
|
|
67
67
|
"vitest": "^2.1.0"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/lalalic/markcut.git"
|
|
68
72
|
}
|
|
69
73
|
}
|
package/skills/markcut/SKILL.md
CHANGED
|
@@ -18,6 +18,13 @@ Everything video is a **stream tree** described with markdown. see [docs/markdow
|
|
|
18
18
|
|
|
19
19
|
- Use `scene` nodes to organize your video. Scenes can nest inside other scenes.
|
|
20
20
|
- Use `description`, `scene.instruction`, `script`, `image|video.prompt` to structure your video content.
|
|
21
|
+
- **If a scene has a `script` (or `audio`) plus one primary visual, the visual MUST be `isBackground:true`.** Without it the visual plays only its own duration (3s default for images) while narration continues — the rest of the scene is a black screen. Scene duration follows the audio, not the image.
|
|
22
|
+
|
|
23
|
+
```md
|
|
24
|
+
## scene-1
|
|
25
|
+
- image prompt:"..." isBackground:true # fills the whole scene, narration decides duration
|
|
26
|
+
- script "..."
|
|
27
|
+
```
|
|
21
28
|
|
|
22
29
|
see [docs/markdown-descriptive.md](docs/markdown-descriptive.md) for full details.
|
|
23
30
|
|
|
@@ -71,15 +78,12 @@ npx @lalalic/markcut spots --waypoints "lat,lng;lat,lng" # discover POIs along a
|
|
|
71
78
|
|
|
72
79
|
---
|
|
73
80
|
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- screenshot some key frames, and understand image to verify intent
|
|
81
|
-
- stt the final video audio, and verify if vtt result is correct
|
|
82
|
-
|
|
81
|
+
## review
|
|
82
|
+
use the review contract defined in [./review.md](./review.md) to guide the review process.
|
|
83
|
+
review as early as possible in the video production process to catch issues before they propagate.
|
|
84
|
+
* review md file
|
|
85
|
+
* review compiled.json
|
|
86
|
+
* review the rendered video
|
|
83
87
|
|
|
84
88
|
## Reference
|
|
85
89
|
|
|
@@ -89,8 +93,7 @@ some common issues (photo or video can't be displayed, audio missing), take belo
|
|
|
89
93
|
| Route / vlog map clips (effects, spots, examples) | [docs/map-dynamic-camera.md](docs/map-dynamic-camera.md) |
|
|
90
94
|
| Built-in components & common npm packages | [docs/components.md](docs/components.md) |
|
|
91
95
|
| Sound effects | [docs/sound-effects.md](docs/sound-effects.md) |
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
| Review contract | [./review.md](./review.md) |
|
|
94
97
|
|
|
95
98
|
## Built-in Components
|
|
96
99
|
|
|
@@ -98,12 +101,6 @@ Built-in components available via `@lalalic/markcut/components`. See [docs/compo
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
## Golden rule
|
|
101
|
-
- always check stream start and duration to avoid
|
|
102
|
-
- audio cut off
|
|
103
|
-
- video cut off
|
|
104
|
-
- subtitle mismatch
|
|
105
|
-
- sync issues between audio, video, and subtitles
|
|
106
|
-
according to the content change. rm `.markcut` will cause all content to be regenerated, which is time consuming and wasteful.
|
|
107
104
|
- put all manual assets in `assets` folder, such as bgm, logo, watermark, etc. don't put them in `.markcut` folder, which is auto generated and will be deleted when `markcut clean` command is run.
|
|
108
105
|
|
|
109
106
|
### Don'ts
|
|
@@ -705,7 +705,7 @@ tween(#000, #FFF) — color interpolation
|
|
|
705
705
|
# Best practices
|
|
706
706
|
- Markcut engine will automatically determine the duration of background streams, video, script audio. Don't set duration for them if no special requirements (e.g., speed up/down on purpose).
|
|
707
707
|
- global streams (BGM, Logo, Lip-sync video,...) should be set on root level, and set `isBackground:true` to let it loop to fill the whole video duration.
|
|
708
|
-
- Set `isBackground:true` for static vision content
|
|
708
|
+
- **Set `isBackground:true` for static vision content (image) whenever the scene has audio/script.** Scene duration is driven by the audio (which can be much longer than the image's default 3s); without `isBackground` the image ends early and the scene tail plays over a black screen while narration continues.
|
|
709
709
|
- adjust audio start time with `start` property to avoid audio cut when transition effect is applied on vision scene
|
|
710
710
|
|
|
711
711
|
|