overlay-factory-worker 0.1.1 → 0.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/README.md +72 -184
- package/package.json +5 -4
- package/scripts/agent.ts +66 -0
- package/scripts/overlay-worker.ts +96 -514
- package/scripts/render-overlay.ts +1 -1
- package/scripts/series.ts +397 -53
- package/scripts/state-dir.ts +12 -9
- package/scripts/stock.ts +2 -0
- package/skills/reference-analysis.md +149 -0
- package/src/Root.tsx +7 -81
- package/src/overlay/Composition.tsx +8 -90
- package/src/overlay/types.ts +11 -57
- package/worker/agent-cli.mjs +277 -0
- package/worker/cli.mjs +3 -3
- package/worker/service.mjs +10 -24
- package/public/fonts/Handjet-variable.woff2 +0 -0
- package/scripts/check-legibility.ts +0 -284
- package/scripts/check-safe-area.ts +0 -148
- package/scripts/export.sh +0 -31
- package/scripts/ig-probe.ts +0 -83
- package/scripts/ig-sync.ts +0 -204
- package/scripts/ingest.sh +0 -34
- package/scripts/library.ts +0 -143
- package/scripts/look-card.ts +0 -107
- package/scripts/look-store.ts +0 -176
- package/scripts/make-card.ts +0 -237
- package/scripts/merge-index.ts +0 -74
- package/scripts/new-episode.ts +0 -149
- package/scripts/place-overlay.ts +0 -359
- package/scripts/prep-card.ts +0 -73
- package/scripts/quality.ts +0 -0
- package/scripts/report.ts +0 -127
- package/scripts/rerender-cards.ts +0 -116
- package/scripts/set-difficulty.ts +0 -62
- package/scripts/verify.ts +0 -149
- package/scripts/wp-restock.ts +0 -281
- package/src/lab/FontLab.tsx +0 -50
- package/src/lab/FontSheet.tsx +0 -188
- package/src/lab/PillLab.tsx +0 -121
- package/src/overlay/DifficultyMeter.tsx +0 -86
- package/src/overlay/PixelText.tsx +0 -134
- package/src/overlay/Title.tsx +0 -75
- package/src/overlay/cardLayout.ts +0 -94
- package/src/overlay/fonts.ts +0 -19
- package/src/overlay/look.ts +0 -155
- package/src/series/what-prints/CodeCard.tsx +0 -107
- package/src/series/what-prints/Composition.tsx +0 -106
- package/src/series/what-prints/codeCardTypes.ts +0 -105
- package/src/series/what-prints/types.ts +0 -22
package/README.md
CHANGED
|
@@ -1,174 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# overlay-factory-worker
|
|
2
|
+
|
|
3
|
+
The Overlay Factory's worker: goosetools.com queues overlay jobs, this machine
|
|
4
|
+
writes the overlays with the agent the user picked in Setup (Claude by default; episode research always uses Claude, for web search), renders them with Remotion, and burns them onto
|
|
5
|
+
the uploaded clip with ffmpeg. Published to npm on its own; lives in the
|
|
6
|
+
goosetools repo at `workers/overlay` (it was the standalone `reel-factory` repo
|
|
7
|
+
until 2026-09-26 — the What Prints series that lived there is at the
|
|
8
|
+
`what-prints-final` tag of github.com/ernkerr/reel-factory).
|
|
9
|
+
|
|
10
|
+
## Series: every look is generated, and taught by notes
|
|
11
|
+
|
|
12
|
+
There are no hand-built formats. A series is made the same way a Carousel style
|
|
13
|
+
is: **add references, it tries, you adjust in words, it learns.**
|
|
14
|
+
|
|
15
|
+
1. **"+ New series"** on Goose Tools queues a `series` job. This machine reads
|
|
16
|
+
the references with Claude and writes `series/<slug>/`:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
series/hot-take/
|
|
20
|
+
series.json the fields the form asks for, where each layer sits, and
|
|
21
|
+
the content brief that lets it write its own episodes
|
|
22
|
+
claim.html self-contained HTML+CSS with {{field}} placeholders
|
|
23
|
+
verdict.html
|
|
24
|
+
notes.md every correction the user has made, oldest first
|
|
25
|
+
episodes.json researched episodes waiting to be used (optional)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. **Preview** draws the series as a still (`preview` job, no clip).
|
|
29
|
+
3. **A note on the preview** ("bigger title", "use the yellow from reference 2")
|
|
30
|
+
queues a `preview` job with `feedback`. `reviseSeries` in
|
|
31
|
+
`scripts/series.ts` gives Claude the current templates, a render of them, the
|
|
32
|
+
original brief and references, every earlier note, and the new one; checks
|
|
33
|
+
the rewrite against the note and retries once with whatever it missed; then
|
|
34
|
+
writes the result as the series. The note is appended to `notes.md`, so it
|
|
35
|
+
keeps applying — to later revisions and to a full rebuild.
|
|
36
|
+
4. **It checks its own render.** After a build or a revision, the series is
|
|
37
|
+
rendered and put next to the references with the shared method's compare
|
|
38
|
+
phase (`skills/reference-analysis.md`, the same method the Carousel Maker
|
|
39
|
+
uses); anything that doesn't match is fixed before you see it. Field keys
|
|
40
|
+
can't change in that step.
|
|
41
|
+
5. The previous 5 versions of each series are kept in `series/.history/<slug>/`,
|
|
42
|
+
so a bad revision can be put back by hand.
|
|
43
|
+
|
|
44
|
+
`series/` is gitignored and per-machine (in an npm install it lives in
|
|
45
|
+
`~/.goosetools/overlay/series`, see `scripts/state-dir.ts`).
|
|
2
46
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
the
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npx tsx scripts/make-card.ts puzzles/x.json # write a NEW code card from a puzzle JSON
|
|
11
|
-
./scripts/ingest.sh # pull assets from ~/Desktop/Automate Editing (via Finder)
|
|
12
|
-
npx tsx scripts/prep-card.ts # code-card JPGs -> transparent rounded PNGs
|
|
13
|
-
npx tsx scripts/new-episode.ts # picks next card + b-roll, places the meter
|
|
14
|
-
npx remotion render WhatPrints out/what-prints-XXX.mp4 --props=episodes/what-prints-XXX.json
|
|
15
|
-
./scripts/export.sh out/what-prints-XXX.mp4 # copy to Desktop/Automate Editing/exports
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Preview while tweaking: `npm run dev` (Remotion Studio).
|
|
19
|
-
|
|
20
|
-
## Episode JSON
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"broll": "assets/b-roll/IMG_4858.MOV",
|
|
25
|
-
"card": "assets/cards/IMG_4030.png",
|
|
26
|
-
"titleLine1": "What",
|
|
27
|
-
"titleLine2": "Prints?",
|
|
28
|
-
"difficulty": "hard", // easy | medium | hard
|
|
29
|
-
"durationSec": 10,
|
|
30
|
-
"revealAtSec": 4, // when the difficulty meter pops in
|
|
31
|
-
"trimBeforeSec": 0, // skip into the b-roll clip
|
|
32
|
-
"meterX": 70, // set by scripts/place-overlay.ts
|
|
33
|
-
"meterY": 670
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
`episodes/index.json` maps every code card to its episode metadata (answer, why,
|
|
38
|
-
caption) extracted from the caption screenshots. `episodes/used.json` tracks
|
|
39
|
-
which cards/b-roll are consumed.
|
|
40
|
-
|
|
41
|
-
## Generating code cards
|
|
42
|
-
|
|
43
|
-
The original cards were phone screenshots of a chat app's code block, cropped by
|
|
44
|
-
`prep-card.ts`. New ones are rendered directly: write a puzzle JSON in `puzzles/`
|
|
45
|
-
and run `scripts/make-card.ts`. It highlights the snippet with Shiki (any
|
|
46
|
-
language), renders the `CodeCard` composition to a transparent rounded PNG at
|
|
47
|
-
`public/assets/cards/<slug>.png`, and upserts the entry into `cards.json` and
|
|
48
|
-
`index.json` — so re-running the same slug edits in place.
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"slug": "nan-trap",
|
|
53
|
-
"name": "Bowser's NaN Trap",
|
|
54
|
-
"language": "Python",
|
|
55
|
-
"code": "score = float(\"nan\")\nbest = score\n\nprint(score == best)",
|
|
56
|
-
"answer": "False",
|
|
57
|
-
"why": "NaN is never equal to itself...",
|
|
58
|
-
"difficulty": "easy"
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Every card is executed before it renders and put through `scripts/quality.ts`:
|
|
63
|
-
the snippet must print exactly what `answer` claims, print the *same thing*
|
|
64
|
-
three runs in a row and on every installed Python 3.11-3.14, be answerable in a
|
|
65
|
-
comment (no memory addresses, nothing over 120 chars), not duplicate an existing
|
|
66
|
-
card, and carry a real `why`. Any failure stops the render; `--no-verify` and
|
|
67
|
-
`--no-gates` override. Audit the whole library with `npx tsx scripts/quality.ts`
|
|
68
|
-
or `npx tsx scripts/verify.ts`.
|
|
69
|
-
|
|
70
|
-
Cards that fail get a `retired` note in `index.json` rather than being deleted —
|
|
71
|
-
`new-episode.ts` skips them, and the audits ignore them.
|
|
72
|
-
|
|
73
|
-
Keep lines under ~45 characters and snippets under ~8 lines — beyond that the
|
|
74
|
-
script shrinks the type to fit and warns. Colors and geometry live in
|
|
75
|
-
`src/series/what-prints/codeCardTypes.ts`; preview with `npm run dev`.
|
|
76
|
-
|
|
77
|
-
The `/what-prints-card` skill writes the puzzles and drives this end to end.
|
|
78
|
-
|
|
79
|
-
## Engagement data
|
|
80
|
-
|
|
81
|
-
`npx tsx scripts/ig-sync.ts` pulls the Instagram history for `@erin.codes` into
|
|
82
|
-
`episodes/performance.json` — captions, likes, comments, and per-post insights
|
|
83
|
-
(reach, saves, shares, views). Read-only; the token comes from
|
|
84
|
-
`~/code/crossposter/.env` (the `ig-auto-dm` one has expired).
|
|
85
|
-
|
|
86
|
-
Insights calls are rate-limited, so by default it only fetches them for posts
|
|
87
|
-
whose caption looks like a series episode. Use `--since=2026-06-01` for a date
|
|
88
|
-
range or `--all` for every reel; results already fetched are reused.
|
|
89
|
-
|
|
90
|
-
`npx tsx scripts/report.ts` then ranks the published episodes against the
|
|
91
|
-
account's own baseline and links each post back to the card it came from
|
|
92
|
-
(captions are the join key; set `permalink` on an `index.json` entry to force it).
|
|
93
|
-
|
|
94
|
-
Judge cards by **comments per 1,000 reach** — reach swings by orders of magnitude
|
|
95
|
-
and likes track reach, so raw likes say more about distribution than about
|
|
96
|
-
whether the puzzle landed.
|
|
97
|
-
|
|
98
|
-
## Overlay Factory
|
|
99
|
-
|
|
100
|
-
The overlay components (`src/overlay/`) are shared: What Prints composites them
|
|
101
|
-
over its own b-roll, and the Overlay Factory tool on Goose Tools burns them onto
|
|
102
|
-
any clip uploaded from a phone.
|
|
103
|
-
|
|
104
|
-
Burn overlays onto a clip locally:
|
|
47
|
+
The hard rule for templates is **no CSS animation**. Remotion renders by seeking
|
|
48
|
+
to each frame and screenshotting, so a CSS animation or transition freezes on
|
|
49
|
+
whatever the first frame painted. Motion comes from the layer's `enter` value
|
|
50
|
+
(`fade`, `slide-up`, `slide-down`, `pop`), which the composition drives with
|
|
51
|
+
Remotion's own spring. Templates also can't carry `<script>` or any external
|
|
52
|
+
URL; `scripts/series.ts` rejects a generation that does, and the composition
|
|
53
|
+
strips scripts again at render time rather than trusting it.
|
|
105
54
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
55
|
+
One-offs (the Single tab) use the same generation with the text baked in and
|
|
56
|
+
nothing kept.
|
|
109
57
|
|
|
110
|
-
|
|
111
|
-
comes from `~/.goosetools/env`, where the Carousel Maker's installer already
|
|
112
|
-
put it — one token serves every Goose Tools worker on a machine:
|
|
58
|
+
## Running it
|
|
113
59
|
|
|
114
60
|
```bash
|
|
115
61
|
npm run overlay:worker # foreground, stops when the terminal closes
|
|
116
62
|
npm run overlay:install # keep it running across reboots (launchd)
|
|
117
63
|
npm run overlay:uninstall # stop it and remove the agent
|
|
64
|
+
npm run overlay -- clip.mov overlay.json out.mp4 # burn a layer file locally
|
|
65
|
+
npm run dev # Remotion Studio
|
|
118
66
|
```
|
|
119
67
|
|
|
120
|
-
|
|
121
|
-
|
|
68
|
+
The token comes from `~/.goosetools/env`, shared by every Goose Tools worker on
|
|
69
|
+
the machine. `overlay:install` writes
|
|
70
|
+
`~/Library/LaunchAgents/com.goosetools.overlay.plist`; logs land in
|
|
122
71
|
`~/.goosetools/overlay-worker.log`. The plist sets PATH explicitly — launchd
|
|
123
72
|
starts with an almost-empty environment, and the render shells out to ffmpeg,
|
|
124
|
-
npx and claude
|
|
125
|
-
|
|
73
|
+
npx and claude.
|
|
74
|
+
|
|
75
|
+
Stop the daemon before running a checkout, or the two split the queue:
|
|
76
|
+
`launchctl bootout gui/$(id -u)/com.goosetools.overlay`.
|
|
77
|
+
|
|
78
|
+
## Rendering
|
|
126
79
|
|
|
127
80
|
Two passes, and the split is the point. Remotion renders **only the overlay**,
|
|
128
81
|
alpha-only to ProRes 4444, at `--scale` so the 1080x1920 composition rasterizes
|
|
129
82
|
natively at the output resolution. ffmpeg then composites that over the
|
|
130
83
|
untouched source. The footage never enters Chrome, so a 4K clip stays 4K and
|
|
131
|
-
renders in seconds rather than minutes — and every coordinate in
|
|
132
|
-
|
|
84
|
+
renders in seconds rather than minutes — and every coordinate stays in
|
|
85
|
+
1080x1920 space no matter the output size.
|
|
133
86
|
|
|
134
87
|
Output is HEVC in `yuv420p`. That pixel format isn't cosmetic: the overlay
|
|
135
88
|
filter hands back BGRA, and an HEVC file in BGRA imports into iOS Photos as a
|
|
136
89
|
file that won't play.
|
|
137
90
|
|
|
138
|
-
## Series
|
|
139
|
-
|
|
140
|
-
What Prints? is special-cased in the worker — it runs your snippet, checks the
|
|
141
|
-
answer against a real run, applies the quality gates, and places the meter
|
|
142
|
-
against the footage. Nothing generated can do that, which is why it stays code.
|
|
143
|
-
|
|
144
|
-
Every other series is generated. Tapping "+ New series" on Goose Tools queues a
|
|
145
|
-
job; this machine reads your reference images with Claude and writes
|
|
146
|
-
`series/<slug>/`:
|
|
147
|
-
|
|
148
|
-
```
|
|
149
|
-
series/hot-take/
|
|
150
|
-
series.json the fields the form asks for, and where each layer sits
|
|
151
|
-
claim.html self-contained HTML+CSS, {{field}} placeholders
|
|
152
|
-
verdict.html
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`series/` is gitignored — each worker builds its own from its own user's
|
|
156
|
-
references, so no two machines have the same set.
|
|
157
|
-
|
|
158
|
-
The hard rule for templates is **no CSS animation**. Remotion renders by seeking
|
|
159
|
-
to each frame and screenshotting, so a CSS animation or transition freezes on
|
|
160
|
-
whatever the first frame painted. Motion comes from the layer's `enter` value
|
|
161
|
-
(`fade`, `slide-up`, `slide-down`, `pop`), which the composition drives with
|
|
162
|
-
Remotion's own spring. Templates also can't carry `<script>` or any external
|
|
163
|
-
URL; `scripts/series.ts` rejects a generation that does, and the composition
|
|
164
|
-
strips scripts again at render time rather than trusting it.
|
|
165
|
-
|
|
166
91
|
## Instagram's safe area
|
|
167
92
|
|
|
168
|
-
`src/overlay/safeArea.ts` holds where Instagram draws over a reel, as
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
card as unsafe when nothing is near them.
|
|
93
|
+
`src/overlay/safeArea.ts` holds where Instagram draws over a reel, as three
|
|
94
|
+
rectangles rather than one inset margin — the action rail only runs down the
|
|
95
|
+
lower half. Layer anchors (`top`, `center`, `bottom`) already respect it.
|
|
172
96
|
|
|
173
97
|
| Zone | Area |
|
|
174
98
|
|---|---|
|
|
@@ -176,40 +100,4 @@ card as unsafe when nothing is near them.
|
|
|
176
100
|
| action rail | x 820-1080, y 1080-1700 |
|
|
177
101
|
| caption | x 0-820, y 1500-1920 |
|
|
178
102
|
|
|
179
|
-
|
|
180
|
-
real overlay bounding boxes out of a render (same two-render diff as the
|
|
181
|
-
legibility check) and reports anything intersecting a zone. It also flags
|
|
182
|
-
elements outside the 3:4 centre crop that the profile grid uses for thumbnails.
|
|
183
|
-
|
|
184
|
-
Numbers are conservative — IG moves its chrome between app versions and notched
|
|
185
|
-
phones differ from flat ones. If something looks wrong on a real phone, adjust
|
|
186
|
-
the zones there and every check follows.
|
|
187
|
-
|
|
188
|
-
## Does it read on a phone?
|
|
189
|
-
|
|
190
|
-
`npx tsx scripts/check-legibility.ts episodes/what-prints-00X.json` renders three
|
|
191
|
-
frames twice — once whole, once with `hideOverlays` so only the background
|
|
192
|
-
draws — diffs them to find the overlay pixels, and measures real contrast.
|
|
193
|
-
|
|
194
|
-
Text (the title) is scored on *local* contrast: strokes against what's
|
|
195
|
-
immediately around them in the finished frame, so the dark halo counts. Panels
|
|
196
|
-
(the code card, the difficulty pill) are scored on how well the opaque block
|
|
197
|
-
separates from the footage, because their internal contrast is fixed by design.
|
|
198
|
-
Thresholds are calibrated against the known-bad title, which measured 4.3:1 and
|
|
199
|
-
would have passed a textbook WCAG bar; the fixed one measures 12.3:1.
|
|
200
|
-
|
|
201
|
-
`new-episode.ts` runs this automatically after placing the meter.
|
|
202
|
-
|
|
203
|
-
## Notes
|
|
204
|
-
|
|
205
|
-
- macOS blocks direct shell reads of `~/Desktop` (TCC). All Desktop I/O goes
|
|
206
|
-
through Finder AppleScript — that's why ingest/export use `osascript`.
|
|
207
|
-
- Exports are silent; add trending audio in Instagram.
|
|
208
|
-
- The title and code card are in fixed positions every episode (title centred
|
|
209
|
-
200px from the top, card centred at 86% width). The difficulty meter has a
|
|
210
|
-
house position too — lower-left at (70, 1400) — and `scripts/place-overlay.ts`
|
|
211
|
-
only moves it when the footage there is more than 25% busier than the calmest
|
|
212
|
-
spot available. The comparison is relative because footage with Erin in frame
|
|
213
|
-
scores high everywhere. Override by editing meterX/meterY.
|
|
214
|
-
- New series (Interviewer, Games): add `src/series/<name>/` with its own
|
|
215
|
-
composition, register it in `src/Root.tsx`, same episode-JSON pattern.
|
|
103
|
+
Numbers are conservative — IG moves its chrome between app versions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlay-factory-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The Goose Tools Overlay Factory worker — your computer renders reel overlays for goosetools.com with Remotion and ffmpeg.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"overlay:install": "node worker/cli.mjs install",
|
|
41
41
|
"overlay:uninstall": "node worker/cli.mjs uninstall",
|
|
42
42
|
"overlay:status": "node worker/cli.mjs status",
|
|
43
|
-
"overlay:doctor": "node worker/cli.mjs doctor"
|
|
43
|
+
"overlay:doctor": "node worker/cli.mjs doctor",
|
|
44
|
+
"prepublishOnly": "node ../sync-shared.mjs --check"
|
|
44
45
|
},
|
|
45
46
|
"sideEffects": [
|
|
46
47
|
"*.css"
|
|
@@ -56,9 +57,9 @@
|
|
|
56
57
|
"worker",
|
|
57
58
|
"scripts",
|
|
58
59
|
"src",
|
|
59
|
-
"public/fonts/Handjet-variable.woff2",
|
|
60
60
|
"remotion.config.ts",
|
|
61
61
|
"tsconfig.json",
|
|
62
|
-
"README.md"
|
|
62
|
+
"README.md",
|
|
63
|
+
"skills"
|
|
63
64
|
]
|
|
64
65
|
}
|
package/scripts/agent.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The overlay worker's two shared dependencies with the other workers:
|
|
3
|
+
*
|
|
4
|
+
* - Which coding-agent CLI to run. The user picks one in Setup on
|
|
5
|
+
* goosetools.com and every claim carries it (`agentCli`); prompts go
|
|
6
|
+
* through worker/agent-cli.mjs, the same adapter the other workers use,
|
|
7
|
+
* synced from workers/shared/agent-cli.js.
|
|
8
|
+
* - How to read references. skills/reference-analysis.md is the Carousel
|
|
9
|
+
* Maker's method, shared so both tools study references, and check their
|
|
10
|
+
* own renders against them, the same way.
|
|
11
|
+
*
|
|
12
|
+
* The worker runs one job at a time, so the agent is set per job with
|
|
13
|
+
* useAgent() rather than threaded through every generation helper.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { pathToFileURL } from "node:url";
|
|
18
|
+
|
|
19
|
+
const ROOT = join(__dirname, "..");
|
|
20
|
+
|
|
21
|
+
type RunAgent = (opts: {
|
|
22
|
+
agent?: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
cwd?: string;
|
|
25
|
+
mode?: "text" | "oneshot" | "read" | "session";
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}) => Promise<{ text: string }>;
|
|
28
|
+
|
|
29
|
+
let current = "claude-code";
|
|
30
|
+
|
|
31
|
+
/** Called at the start of each job with the agent that job's owner picked. */
|
|
32
|
+
export const useAgent = (agentCli?: string | null) => {
|
|
33
|
+
current = agentCli || "claude-code";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let adapter: Promise<{ runAgent: RunAgent }> | undefined;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run one prompt through the current agent and return its text.
|
|
40
|
+
*
|
|
41
|
+
* "read" lets it open the reference and render images named in the prompt;
|
|
42
|
+
* "oneshot" is for judgements where everything it needs is inlined.
|
|
43
|
+
*/
|
|
44
|
+
export const askAgent = async (
|
|
45
|
+
prompt: string,
|
|
46
|
+
{ mode = "read", timeoutMs = 15 * 60 * 1000 }: { mode?: "read" | "oneshot"; timeoutMs?: number } = {},
|
|
47
|
+
): Promise<string> => {
|
|
48
|
+
// A file URL rather than a bare specifier: the adapter is an ES module in a
|
|
49
|
+
// CommonJS package, and this keeps it a real dynamic import.
|
|
50
|
+
adapter ??= import(pathToFileURL(join(ROOT, "worker", "agent-cli.mjs")).href) as Promise<{
|
|
51
|
+
runAgent: RunAgent;
|
|
52
|
+
}>;
|
|
53
|
+
const { runAgent } = await adapter;
|
|
54
|
+
const { text } = await runAgent({ agent: current, prompt, mode, cwd: ROOT, timeoutMs });
|
|
55
|
+
return text;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let method: string | undefined;
|
|
59
|
+
|
|
60
|
+
/** One section of the shared reference method, by its SECTION marker. */
|
|
61
|
+
export const skillSection = (name: string): string => {
|
|
62
|
+
method ??= readFileSync(join(ROOT, "skills", "reference-analysis.md"), "utf8");
|
|
63
|
+
const m = method.match(new RegExp(`<!-- SECTION: ${name} -->\\n([\\s\\S]*?)(?=<!-- SECTION: |$)`));
|
|
64
|
+
if (!m) throw new Error(`reference method section "${name}" not found`);
|
|
65
|
+
return m[1].trim();
|
|
66
|
+
};
|