transitions-refine 0.3.9 → 0.3.11
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/.agents/skills/refine-live/SKILL.md +118 -65
- package/README.md +40 -13
- package/bin/cli.mjs +167 -58
- package/demo.html +1252 -195
- package/package.json +2 -1
- package/server/motion-tokens.mjs +116 -2
- package/server/relay.mjs +195 -15
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: refine-live
|
|
3
|
-
description:
|
|
3
|
+
description: In-chat fallback for the Timeline Inspector Refine agent. Use when the user runs `/refine live`, asks to "refine live", "go live", or answer refine jobs — but ONLY when no persistent agent is wired (no `npx transitions-refine live`). Prefer `npx transitions-refine live` for run-and-forget (relay spawns agent per click, no idle credit burn). This skill long-polls the relay, posts suggestions, handles scan/apply jobs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Refine Live
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Two modes
|
|
9
|
+
|
|
10
|
+
**Persistent (recommended — run and forget)**
|
|
11
|
+
Run `npx transitions-refine live` from your project. The CLI starts the relay and wires `REFINE_AGENT_CMD` so the relay spawns your agent CLI **per Refine click**. No chat loop; idle = zero credit burn. Works hours later as long as the relay process keeps running. Stop with Ctrl-C (or `npx transitions-refine stop`).
|
|
12
|
+
|
|
13
|
+
**In-chat loop (fallback — this skill)**
|
|
14
|
+
Run `/refine live` in Cursor/Claude/Codex when the relay is up but has **no** `REFINE_AGENT_CMD`. **You** become the poller via `GET /jobs/next`. The Agent tab stays available only while you keep polling — **each idle poll cycle consumes chat turns/credits**. Say "stop refine" to exit.
|
|
15
|
+
|
|
16
|
+
Use the in-chat loop only when you cannot wire a persistent agent CLI.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Turn yourself into the LLM behind the Timeline Inspector's **Refine** button (**in-chat fallback mode**). While
|
|
9
21
|
this loop runs, the panel's **LLM** tab is "available": each click sends one
|
|
10
22
|
transition here, you reason about it, and your suggestions appear in the panel.
|
|
11
23
|
|
|
@@ -19,15 +31,28 @@ Browser (Refine, LLM tab) ──POST /jobs──► relay ──GET /jobs/next
|
|
|
19
31
|
◄──GET /jobs/:id── relay ◄──POST /jobs/:id/result── YOU
|
|
20
32
|
```
|
|
21
33
|
|
|
22
|
-
## The loop — stay live
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
## The loop — stay live, but don't burn credits forever
|
|
35
|
+
|
|
36
|
+
Keep polling so the panel's LLM tab stays "available", but this loop costs chat
|
|
37
|
+
turns/credits even while idle, so it is **not** truly run-and-forget — it has
|
|
38
|
+
three exits, in priority order:
|
|
39
|
+
|
|
40
|
+
1. **Relay stop signal (authoritative).** `GET /jobs/next` may return `200` with
|
|
41
|
+
`{"stop": true}`. The relay sends this when the user clicks **Stop** in the
|
|
42
|
+
panel, or automatically after ~10 min with no jobs. **Always honor it: stop
|
|
43
|
+
looping immediately**, tell the user the LLM tab will go unavailable and how to
|
|
44
|
+
resume (`/refine live`), and end your turn. Never re-poll after a stop signal.
|
|
45
|
+
2. **The user says so** — "stop refine", "exit live", etc.
|
|
46
|
+
3. **Your own idle backoff (safety net).** A long stretch of `204`s is normal —
|
|
47
|
+
it just means no one has clicked Refine yet — but to avoid spending credits on
|
|
48
|
+
a forgotten loop, **back off as idle grows** instead of hammering immediately:
|
|
49
|
+
re-poll right away for the first few empty cycles, then pause ~5s between polls,
|
|
50
|
+
and after ~10 min of unbroken idle stop on your own (same as the relay's
|
|
51
|
+
auto-stop) and tell the user how to resume. Any real job resets the backoff.
|
|
52
|
+
|
|
53
|
+
The relay reports the agent as "available" for ~120s after your last poll, so
|
|
54
|
+
short pauses keep you live. A successful job always resets idle, so an active
|
|
55
|
+
session never backs off.
|
|
31
56
|
|
|
32
57
|
1. **Claim the next job (long-poll).** This call blocks up to ~25s, then returns.
|
|
33
58
|
|
|
@@ -35,8 +60,12 @@ never has to re-run `/refine live`.
|
|
|
35
60
|
curl -s http://localhost:7331/jobs/next
|
|
36
61
|
```
|
|
37
62
|
|
|
38
|
-
- HTTP `204` / empty body → no work yet.
|
|
39
|
-
|
|
63
|
+
- HTTP `204` / empty body → no work yet. Poll again, applying the idle
|
|
64
|
+
backoff above (immediate at first, then ~5s pauses, then stop after ~10 min).
|
|
65
|
+
- HTTP `200` with `{"stop": true}` → **the loop must end.** Stop polling, tell
|
|
66
|
+
the user the LLM tab is now unavailable and that `/refine live` resumes it,
|
|
67
|
+
and end your turn. Do not treat it as a job.
|
|
68
|
+
- HTTP `200` with a job JSON → work to do. Shape:
|
|
40
69
|
|
|
41
70
|
```json
|
|
42
71
|
{
|
|
@@ -64,11 +93,11 @@ never has to re-run `/refine live`.
|
|
|
64
93
|
posting suggestions. Everything below (refineType, steps 3–4) is for the
|
|
65
94
|
normal Refine flow.
|
|
66
95
|
- `refineType` chooses what kinds of suggestions to make (it mirrors the
|
|
67
|
-
panel's two tabs)
|
|
96
|
+
panel's two tabs). The tabs scan **independently**, so answer only the one
|
|
97
|
+
you were asked for:
|
|
68
98
|
- `"small"` (or missing) → **Small refinements**: nudge the existing
|
|
69
|
-
declarations toward the motion tokens (step 3a) **
|
|
70
|
-
|
|
71
|
-
transitions.dev recipe (step 3b).
|
|
99
|
+
declarations toward the motion tokens **only** (step 3a). Do **not**
|
|
100
|
+
propose a recipe swap here — that's the Replace tab's separate job.
|
|
72
101
|
- `"replace"` → **Replace transition**: suggest a whole-transition recipe
|
|
73
102
|
swap **only** (step 3b). Do **not** propose motion-token tweaks — skip
|
|
74
103
|
step 3a entirely.
|
|
@@ -81,50 +110,74 @@ never has to re-run `/refine live`.
|
|
|
81
110
|
-d '{"message":"Matching to transitions.dev motion tokens…"}'
|
|
82
111
|
```
|
|
83
112
|
|
|
84
|
-
3. **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- `
|
|
113
|
+
3. **Answer in ONE shot — speed matters.** Each click should feel instant, so
|
|
114
|
+
resolve the job from the data below plus what's already in this skill. Do **not**
|
|
115
|
+
spawn subagents or run a broad codebase search, and do **not** open the
|
|
116
|
+
transitions-dev `SKILL.md` — its tokens and decision rules are inlined here.
|
|
117
|
+
- `refineType === "small"` → step 3a only, with **zero file reads**.
|
|
118
|
+
- `refineType === "replace"` → step 3b only; open **at most the one** recipe
|
|
119
|
+
reference file you choose.
|
|
88
120
|
|
|
89
121
|
First, infer each declaration's **usage** from `label` + `selector` (modal
|
|
90
|
-
close, dropdown open, tooltip, badge, resize, color/theme change…).
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
close, dropdown open, tooltip, badge, resize, color/theme change…). Match on
|
|
123
|
+
**intent, not the nearest number**.
|
|
124
|
+
|
|
125
|
+
**3a. Motion-token tweaks (`refineType === "small"` only — no file reads).**
|
|
126
|
+
Pick the token that fits the usage and propose a change **only where the current
|
|
127
|
+
value actually differs**.
|
|
128
|
+
|
|
129
|
+
- **Durations:** 40ms Stagger (per-item offset) · 80ms Micro (tooltip delay,
|
|
130
|
+
shake segment) · 150ms Quick (modal/dropdown close, text swap, tooltip
|
|
131
|
+
appear) · 250ms Fast (icon swap, dropdown/modal open, tabs slide, page
|
|
132
|
+
slide) · 350ms Medium (panel/toast close) · 400ms Slow (panel open, skeleton
|
|
133
|
+
reveal, input clear) · 500ms Very slow (emphasis, badge appear, text reveal,
|
|
134
|
+
success check).
|
|
135
|
+
- **Default easing — "Smooth ease out":** `cubic-bezier(0.22, 1, 0.36, 1)`
|
|
136
|
+
(modal/dropdown/panel open+close, page slide, resize, position change).
|
|
137
|
+
- **Other on-grid easings — LEAVE UNCHANGED:** `ease-out` (tooltip),
|
|
138
|
+
`ease-in-out` (icon/text swap, text reveal, skeleton reveal), `linear`
|
|
139
|
+
(shimmer, pulse, spinner), `cubic-bezier(0.34, 1.36, 0.64, 1)` (badge pop),
|
|
140
|
+
`cubic-bezier(0.34, 3.85, 0.64, 1)` (avatar return).
|
|
141
|
+
- **Nudge toward Smooth ease out:** generic `ease`, `ease-in`, or any
|
|
142
|
+
hand-rolled cubic-bezier()/linear() that isn't a token above.
|
|
143
|
+
|
|
144
|
+
**3b. Whole-transition recipe swap (`refineType === "replace"` only — no file
|
|
145
|
+
reads).** Match the inferred usage to ONE recipe below (this list *is* the
|
|
146
|
+
decision rules — no SKILL.md or reference-file read needed). Emit ONE
|
|
147
|
+
`kind: "replace"` suggestion whose `patch` carries the **motion-token**
|
|
148
|
+
duration/easing for the recipe's phase (open vs close) on the property that
|
|
149
|
+
already transitions (or `"all"`), with a `reference` field naming the file and
|
|
150
|
+
the recipe in `title` + `reason`. The patch only drives the live preview —
|
|
151
|
+
exact keyframes/structure come from the user pasting that reference file, so you
|
|
152
|
+
never need to open it. If no recipe genuinely fits the usage, return an
|
|
153
|
+
**empty** `suggestions` array with a short `summary`.
|
|
154
|
+
|
|
155
|
+
- Card resize — a container changes width/height on a layout change (`01-card-resize.md`)
|
|
156
|
+
- Number pop-in — a number/digit updates (`02-number-pop-in.md`)
|
|
157
|
+
- Notification badge — a small dot/badge appears on a trigger (`03-notification-badge.md`)
|
|
158
|
+
- Text states swap — text content changes in place (`04-text-states-swap.md`)
|
|
159
|
+
- Menu dropdown — an anchored surface grows from its trigger (`05-menu-dropdown.md`)
|
|
160
|
+
- Modal open/close — a centered dialog scales up, softer scale-down on close (`06-modal.md`)
|
|
161
|
+
- Panel reveal — a surface slides into a region with a cross-blur (`07-panel-reveal.md`)
|
|
162
|
+
- Page side-by-side — slide between list↔detail or step 1↔step 2 (`08-page-side-by-side.md`)
|
|
163
|
+
- Icon swap — two icons cross-fade in the same slot (`09-icon-swap.md`)
|
|
164
|
+
- Success check — a checkmark celebration: fade + rotate + bob + stroke-draw (`10-success-check.md`)
|
|
165
|
+
- Avatar group hover — hover lifts an item in a horizontal stack (`11-avatar-group-hover.md`)
|
|
166
|
+
- Error state shake — invalid-input shake (`12-error-state-shake.md`)
|
|
167
|
+
- Input clear with dissolve — clearing a text field (`13-input-clear-dissolve.md`)
|
|
168
|
+
- Skeleton loader and reveal — placeholder pulses then swaps to real content (`14-skeleton-reveal.md`)
|
|
169
|
+
- Shimmer text — in-progress / "thinking" text shimmer (`15-shimmer-text.md`)
|
|
170
|
+
- Tabs sliding — a moving highlight across segmented options (`16-tabs-sliding.md`)
|
|
171
|
+
- Tooltip open/close — delayed fade+scale in, instant out (`17-tooltip.md`)
|
|
172
|
+
- Texts reveal — staggered blurred rise of stacked text lines (`18-texts-reveal.md`)
|
|
173
|
+
- Card hover tilt — 3D tilt toward the pointer (`19-card-tilt.md`)
|
|
174
|
+
- Plus to menu morph — a circular trigger becomes the surface it opens (`20-plus-menu-morph.md`)
|
|
175
|
+
- Accordion expand — a collapsible body grows/shrinks in height (`21-accordion.md`)
|
|
176
|
+
|
|
177
|
+
Tie-break: prefer the lower-overhead recipe (card resize over panel reveal,
|
|
178
|
+
dropdown over modal). Only propose a swap when the current declarations are
|
|
179
|
+
clearly a hand-rolled version of a recipe or are missing the structure the usage
|
|
180
|
+
calls for; if the transition already *is* the right recipe, return empty.
|
|
128
181
|
|
|
129
182
|
4. **Post the result** (this completes the job and renders cards in the panel):
|
|
130
183
|
|
|
@@ -148,9 +201,8 @@ never has to re-run `/refine live`.
|
|
|
148
201
|
}'
|
|
149
202
|
```
|
|
150
203
|
|
|
151
|
-
The example above is a `small` job (token tweaks).
|
|
152
|
-
|
|
153
|
-
`small`, or as the **only** suggestion for `replace`:
|
|
204
|
+
The example above is a `small` job (token tweaks only). A `replace` job instead
|
|
205
|
+
returns a single `kind: "replace"` card as its **only** suggestion:
|
|
154
206
|
|
|
155
207
|
```json
|
|
156
208
|
{
|
|
@@ -174,9 +226,10 @@ never has to re-run `/refine live`.
|
|
|
174
226
|
-H 'Content-Type: application/json' -d '{"message":"…"}'
|
|
175
227
|
```
|
|
176
228
|
|
|
177
|
-
5. **Go back to step 1.** Keep looping
|
|
178
|
-
|
|
179
|
-
|
|
229
|
+
5. **Go back to step 1.** Keep looping, but honor the three exits from
|
|
230
|
+
[the loop section](#the-loop--stay-live-but-dont-burn-credits-forever): a
|
|
231
|
+
`{"stop": true}` from the relay, the user telling you to stop, or your own idle
|
|
232
|
+
backoff/auto-stop after ~10 min quiet. A real job resets idle. Whenever you do
|
|
180
233
|
stop, tell them the LLM tab will go unavailable and how to restart
|
|
181
234
|
(`/refine live`).
|
|
182
235
|
|
package/README.md
CHANGED
|
@@ -29,27 +29,30 @@ npx transitions-refine stop
|
|
|
29
29
|
|
|
30
30
|
1. injects one `<script type="module" src=".../inject.js">` into your page (it looks for `index.html`, `public/index.html`, … or pass `--page <path>`),
|
|
31
31
|
2. drops the `refine-live` + `transitions-dev` skills into `.agents/skills/` (so the agent makes token-aware picks),
|
|
32
|
-
3.
|
|
32
|
+
3. **auto-wires an LLM backend** — it detects the agent hosting this run and points the relay at *its* CLI, so Refine uses the subscription you already have (Cursor → `cursor-agent`, Claude Code → `claude`, Codex → `codex`),
|
|
33
|
+
4. starts the local relay (which serves the panel at `/inject.js`).
|
|
33
34
|
|
|
34
35
|
Open your app — the panel is now on the page. Press Ctrl-C to stop the relay and remove the injected tag.
|
|
35
36
|
|
|
36
37
|
## LLM quality (recommended)
|
|
37
38
|
|
|
38
|
-
The default answerer snaps each value to the nearest motion token. For *usage-aware* picks (a 300 ms modal close → `Quick` 150 ms, a dropdown open → `Fast` 250 ms), back the panel with an LLM.
|
|
39
|
+
The default answerer snaps each value to the nearest motion token. For *usage-aware* picks (a 300 ms modal close → `Quick` 150 ms, a dropdown open → `Fast` 250 ms), back the panel with an LLM.
|
|
40
|
+
|
|
41
|
+
Plain `npx transitions-refine live` already does this when an agent CLI is available: it prefers the **host agent** so it bills your existing plan, persistently (no `/refine live` loop to keep alive). The CLI must be authenticated once — Cursor: run `cursor-agent` to log in (or set `CURSOR_API_KEY`); Claude Code: run `claude` to sign in; Codex: run `codex` to sign in (or set `CODEX_API_KEY`).
|
|
39
42
|
|
|
40
43
|
```bash
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
npx transitions-refine live --llm
|
|
44
|
-
```
|
|
44
|
+
# auto: use whichever agent hosts this run (cursor-agent / claude / codex)
|
|
45
|
+
npx transitions-refine live
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
# force a specific agent regardless of host
|
|
48
|
+
npx transitions-refine live --agent claude # cursor | claude | codex
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/refine live
|
|
50
|
+
# no agent on the machine? install the Cursor CLI as a fallback
|
|
51
|
+
npx transitions-refine live --llm
|
|
51
52
|
```
|
|
52
53
|
|
|
54
|
+
Resolution order: `REFINE_AGENT_CMD` (explicit) → `--agent <name>` → detected host agent → any installed agent → (with `--llm`) install cursor-agent. If none is available the panel falls back to the in-IDE loop — run `/refine live` in your editor to answer jobs yourself (works in Cursor, Claude Code, or Codex; stays live only while that session keeps polling).
|
|
55
|
+
|
|
53
56
|
You can also point the relay at any one-shot agent CLI via `REFINE_AGENT_CMD` (the relay feeds it the prompt on stdin and reads a JSON result from stdout):
|
|
54
57
|
|
|
55
58
|
```bash
|
|
@@ -102,6 +105,30 @@ Endpoints: `POST /jobs` (refine, `kind: "apply"`, or `kind: "scan"`), `GET /jobs
|
|
|
102
105
|
|
|
103
106
|
Refine suggestions stay as live overrides until you press **Accept**, which is the explicit step that writes them into your source.
|
|
104
107
|
|
|
105
|
-
## License
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
## Terms & License
|
|
109
|
+
|
|
110
|
+
> Full terms: https://transitions.dev/terms.html
|
|
111
|
+
|
|
112
|
+
- **Beta software.** Refine is an early Beta. Features, commands, the panel, and
|
|
113
|
+
its behavior may change, regress, or be removed at any time without notice. No
|
|
114
|
+
guarantee of availability, stability, or fitness for any purpose.
|
|
115
|
+
- **Your agent credits.** Refine triggers *your own* AI coding agent (Cursor,
|
|
116
|
+
Claude Code, Codex, …). Every Refine click and any `npx transitions-refine
|
|
117
|
+
live` / `/refine live` session consumes *your* provider's tokens/credits —
|
|
118
|
+
including while a live session sits idle and keeps polling. You are solely
|
|
119
|
+
responsible for that spend; this project is not liable for and will not
|
|
120
|
+
reimburse any credits, fees, or overages. Run `npx transitions-refine stop`
|
|
121
|
+
(or say `stop refine`) when you're done.
|
|
122
|
+
- **The agent changes your code.** Accepting a suggestion writes changes into
|
|
123
|
+
your source files. Suggestions are AI-generated and may be wrong. Use version
|
|
124
|
+
control, review every change before committing, and keep backups. Use at your
|
|
125
|
+
own risk.
|
|
126
|
+
- **No warranty.** The software is provided **"AS IS"**, without warranty of any
|
|
127
|
+
kind. To the maximum extent permitted by law, the authors are not liable for
|
|
128
|
+
any damages — direct, indirect, incidental, or consequential, including lost
|
|
129
|
+
work or lost credits — arising from its use.
|
|
130
|
+
|
|
131
|
+
### MIT License
|
|
132
|
+
|
|
133
|
+
MIT — Copyright (c) 2026 Jakub Antalik / Transitions.dev. See
|
|
134
|
+
https://transitions.dev/terms.html for the full license text.
|
package/bin/cli.mjs
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Refine — transitions.dev live tool.
|
|
3
3
|
//
|
|
4
|
-
// npx transitions-refine live
|
|
5
|
-
// npx transitions-refine live --
|
|
6
|
-
// npx transitions-refine
|
|
4
|
+
// npx transitions-refine live # inject panel + relay; auto-wire your agent CLI
|
|
5
|
+
// npx transitions-refine live --agent claude # force an agent: cursor | claude | codex
|
|
6
|
+
// npx transitions-refine live --llm # install the Cursor CLI if no agent is found
|
|
7
|
+
// npx transitions-refine stop # remove the injected <script> tag
|
|
7
8
|
//
|
|
8
9
|
// `live` sets up the timeline + Refine with no npm install and no source edits
|
|
9
10
|
// of your own:
|
|
10
11
|
// 1. injects one <script type="module" src=".../inject.js"> into your page
|
|
11
12
|
// 2. drops the `refine-live` + `transitions-dev` skills (for token-aware picks)
|
|
12
|
-
// 3.
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// 3. wires an LLM backend so the relay answers jobs itself, persistently (no
|
|
14
|
+
// /refine live loop). It prefers the agent HOSTING this run — Cursor →
|
|
15
|
+
// cursor-agent, Claude Code → claude, Codex → codex — so Refine uses the
|
|
16
|
+
// subscription you already have. Override with --agent <name> or by
|
|
17
|
+
// exporting REFINE_AGENT_CMD. With no agent available it falls back to the
|
|
18
|
+
// /refine live in-IDE loop (and --llm can install the Cursor CLI).
|
|
16
19
|
// 4. starts the local refine relay (serves the panel at /inject.js).
|
|
17
20
|
|
|
18
21
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -48,6 +51,7 @@ function parseArgs(argv) {
|
|
|
48
51
|
const a = argv[i];
|
|
49
52
|
if (a === "--page" || a === "-p") args.page = argv[++i];
|
|
50
53
|
else if (a === "--port") args.port = argv[++i];
|
|
54
|
+
else if (a === "--agent") args.agent = argv[++i];
|
|
51
55
|
else if (a.startsWith("--")) args[a.slice(2)] = true;
|
|
52
56
|
else args._.push(a);
|
|
53
57
|
}
|
|
@@ -111,17 +115,68 @@ function dropSkill(name) {
|
|
|
111
115
|
return existed ? "updated" : true;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
// ── agent
|
|
115
|
-
// The relay
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
// ── agent CLIs (for the persistent LLM path) ─────────────────────────────────
|
|
119
|
+
// The relay answers LLM jobs by spawning REFINE_AGENT_CMD per job (stdin =
|
|
120
|
+
// prompt, stdout = JSON). To bill against the account the user ALREADY pays for,
|
|
121
|
+
// we detect which agent is hosting this run and wire ITS CLI: a Claude Code user
|
|
122
|
+
// gets `claude`, a Codex user gets `codex`, a Cursor user gets `cursor-agent`.
|
|
123
|
+
// Detection is by each host's env markers; override with --agent <name> or by
|
|
124
|
+
// exporting REFINE_AGENT_CMD yourself.
|
|
125
|
+
const envHasPrefix = (p) => Object.keys(process.env).some((k) => k.startsWith(p));
|
|
126
|
+
|
|
127
|
+
const AGENTS = [
|
|
128
|
+
{
|
|
129
|
+
key: "cursor",
|
|
130
|
+
label: "Cursor",
|
|
131
|
+
// Cursor's agent terminal exports CURSOR_AGENT.
|
|
132
|
+
host: () => Boolean(process.env.CURSOR_AGENT),
|
|
133
|
+
bins: [
|
|
134
|
+
"cursor-agent",
|
|
135
|
+
join(HOME, ".local/bin/cursor-agent"),
|
|
136
|
+
join(HOME, ".cursor/bin/cursor-agent"),
|
|
137
|
+
],
|
|
138
|
+
// -p = headless/stdin, --force = auto-allow tool calls. The relay also
|
|
139
|
+
// auto-appends -p/--trust/--force for cursor-agent; we wire them up front so
|
|
140
|
+
// the printed command is the real one.
|
|
141
|
+
cmd: (bin) => `${bin} -p --force`,
|
|
142
|
+
canInstall: true,
|
|
143
|
+
auth: "run `cursor-agent` once to log in, or set CURSOR_API_KEY",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "claude",
|
|
147
|
+
label: "Claude Code",
|
|
148
|
+
// Claude Code exports CLAUDECODE=1 (+ CLAUDE_CODE_*) in its tools/terminals.
|
|
149
|
+
host: () => Boolean(process.env.CLAUDECODE || process.env.CLAUDE_CODE_ENTRYPOINT),
|
|
150
|
+
bins: [
|
|
151
|
+
"claude",
|
|
152
|
+
join(HOME, ".claude/local/claude"),
|
|
153
|
+
join(HOME, ".local/bin/claude"),
|
|
154
|
+
],
|
|
155
|
+
// -p = headless print (prompt on stdin); skip-permissions so apply jobs can
|
|
156
|
+
// edit files without an interactive approval prompt.
|
|
157
|
+
cmd: (bin) => `${bin} -p --dangerously-skip-permissions`,
|
|
158
|
+
canInstall: false,
|
|
159
|
+
auth: "run `claude` once to sign in",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "codex",
|
|
163
|
+
label: "Codex",
|
|
164
|
+
// Codex exec exports CODEX_SANDBOX (+ CODEX_* friends) in its sandbox.
|
|
165
|
+
host: () => Boolean(process.env.CODEX_SANDBOX) || envHasPrefix("CODEX_"),
|
|
166
|
+
bins: ["codex", join(HOME, ".local/bin/codex")],
|
|
167
|
+
// `codex exec -` reads the prompt on stdin; workspace-write so apply jobs can
|
|
168
|
+
// edit files; skip-git-repo-check so a non-git project root doesn't error out.
|
|
169
|
+
cmd: (bin) => `${bin} exec --sandbox workspace-write --skip-git-repo-check -`,
|
|
170
|
+
canInstall: false,
|
|
171
|
+
auth: "run `codex` once to sign in, or set CODEX_API_KEY",
|
|
172
|
+
},
|
|
123
173
|
];
|
|
124
174
|
|
|
175
|
+
// Host-detection precedence. Claude/Codex export very specific markers; check
|
|
176
|
+
// them BEFORE Cursor so a Claude Code or Codex session launched from inside a
|
|
177
|
+
// Cursor terminal (which still carries CURSOR_*) is not mis-wired to cursor-agent.
|
|
178
|
+
const HOST_PRECEDENCE = ["claude", "codex", "cursor"];
|
|
179
|
+
|
|
125
180
|
function isRunnable(bin) {
|
|
126
181
|
try {
|
|
127
182
|
return spawnSync(bin, ["--version"], { stdio: "ignore" }).status === 0;
|
|
@@ -130,11 +185,20 @@ function isRunnable(bin) {
|
|
|
130
185
|
}
|
|
131
186
|
}
|
|
132
187
|
|
|
133
|
-
function
|
|
134
|
-
return
|
|
188
|
+
function findBin(agent) {
|
|
189
|
+
return agent.bins.find(isRunnable) || null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function detectHostAgent() {
|
|
193
|
+
for (const key of HOST_PRECEDENCE) {
|
|
194
|
+
const a = AGENTS.find((x) => x.key === key);
|
|
195
|
+
if (a && a.host()) return a;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
135
198
|
}
|
|
136
199
|
|
|
137
|
-
|
|
200
|
+
// Install the Cursor CLI (the only agent we can fetch non-interactively).
|
|
201
|
+
function installCursorCli() {
|
|
138
202
|
log("• installing the Cursor CLI (cursor-agent) — one-time…");
|
|
139
203
|
const r =
|
|
140
204
|
process.platform === "win32"
|
|
@@ -147,15 +211,53 @@ function installAgentCli() {
|
|
|
147
211
|
stdio: "inherit",
|
|
148
212
|
});
|
|
149
213
|
if (r.status !== 0) log("! the installer exited non-zero — see its output above.");
|
|
150
|
-
return
|
|
214
|
+
return findBin(AGENTS[0]);
|
|
151
215
|
}
|
|
152
216
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
217
|
+
// Decide which agent CLI the relay should spawn. Returns either
|
|
218
|
+
// { cmd, agent, source } → wire this command (persistent LLM), or
|
|
219
|
+
// { cmd:null, agent?, reason } → couldn't wire; caller prints guidance.
|
|
220
|
+
// Precedence: explicit REFINE_AGENT_CMD → --agent <key> → host agent (same
|
|
221
|
+
// subscription) → any installed agent → (with --llm) install cursor-agent.
|
|
222
|
+
function resolveAgent({ wantLlm, forceKey }) {
|
|
223
|
+
if (process.env.REFINE_AGENT_CMD) {
|
|
224
|
+
return { cmd: process.env.REFINE_AGENT_CMD, source: "env" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let target = null;
|
|
228
|
+
if (forceKey) {
|
|
229
|
+
target = AGENTS.find((a) => a.key === forceKey) || null;
|
|
230
|
+
if (!target) {
|
|
231
|
+
return { cmd: null, reason: `unknown --agent "${forceKey}" (use cursor | claude | codex)` };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!target) target = detectHostAgent();
|
|
235
|
+
|
|
236
|
+
if (target) {
|
|
237
|
+
let bin = findBin(target);
|
|
238
|
+
if (!bin && target.canInstall && wantLlm) bin = installCursorCli();
|
|
239
|
+
if (bin) return { cmd: target.cmd(bin), agent: target, source: forceKey ? "forced" : "host" };
|
|
240
|
+
return {
|
|
241
|
+
cmd: null,
|
|
242
|
+
agent: target,
|
|
243
|
+
reason:
|
|
244
|
+
`detected ${target.label} but its CLI isn't on PATH` +
|
|
245
|
+
(target.canInstall ? " — re-run with --llm to install it" : ` — install the ${target.label} CLI first`),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// No host detected (plain terminal): use any installed agent, in list order.
|
|
250
|
+
for (const a of AGENTS) {
|
|
251
|
+
const bin = findBin(a);
|
|
252
|
+
if (bin) return { cmd: a.cmd(bin), agent: a, source: "scan" };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Nothing installed: only cursor-agent can be fetched non-interactively.
|
|
256
|
+
if (wantLlm) {
|
|
257
|
+
const bin = installCursorCli();
|
|
258
|
+
if (bin) return { cmd: AGENTS[0].cmd(bin), agent: AGENTS[0], source: "install" };
|
|
259
|
+
}
|
|
260
|
+
return { cmd: null, reason: "no agent CLI found" };
|
|
159
261
|
}
|
|
160
262
|
|
|
161
263
|
function log(msg) {
|
|
@@ -186,26 +288,28 @@ function cmdLive(args) {
|
|
|
186
288
|
else if (r === "exists") log(`✓ ${name} skill already present (v${PKG_VERSION})`);
|
|
187
289
|
}
|
|
188
290
|
|
|
189
|
-
// 2.5)
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
291
|
+
// 2.5) wire an agent CLI so the relay can answer LLM jobs itself — the
|
|
292
|
+
// persistent path (no `/refine live` loop to keep alive). We prefer the
|
|
293
|
+
// agent hosting this run so Refine bills the subscription the user already
|
|
294
|
+
// has. REFINE_AGENT_CMD (if set) always wins; --agent forces a choice.
|
|
193
295
|
const wantLlm = Boolean(args.llm);
|
|
296
|
+
const forceKey = typeof args.agent === "string" ? args.agent : null;
|
|
194
297
|
const env = { ...process.env, REFINE_RELAY_PORT: port };
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
log(`✓ LLM path wired: relay will spawn ${cmd}`);
|
|
298
|
+
const resolved = resolveAgent({ wantLlm, forceKey });
|
|
299
|
+
if (resolved.cmd) {
|
|
300
|
+
env.REFINE_AGENT_CMD = resolved.cmd;
|
|
301
|
+
if (resolved.source === "env") {
|
|
302
|
+
log(`✓ using REFINE_AGENT_CMD from environment: ${resolved.cmd}`);
|
|
303
|
+
} else {
|
|
304
|
+
const via =
|
|
305
|
+
resolved.source === "host" ? `detected ${resolved.agent.label}`
|
|
306
|
+
: resolved.source === "forced" ? `forced ${resolved.agent.label}`
|
|
307
|
+
: resolved.source === "scan" ? `found ${resolved.agent.label}`
|
|
308
|
+
: `installed ${resolved.agent.label}`;
|
|
309
|
+
log(`✓ LLM path wired (${via}): relay will spawn ${resolved.cmd}`);
|
|
208
310
|
}
|
|
311
|
+
} else if (resolved.reason) {
|
|
312
|
+
log(`• LLM not wired — ${resolved.reason}.`);
|
|
209
313
|
}
|
|
210
314
|
|
|
211
315
|
// 3) start the relay (foreground; Ctrl-C stops it + reverts the injection)
|
|
@@ -215,6 +319,7 @@ function cmdLive(args) {
|
|
|
215
319
|
});
|
|
216
320
|
|
|
217
321
|
const llmWired = Boolean(env.REFINE_AGENT_CMD);
|
|
322
|
+
const authHint = resolved.agent && resolved.agent.auth;
|
|
218
323
|
log("");
|
|
219
324
|
log("Next:");
|
|
220
325
|
log(" 1. Open your app — the timeline panel is now on the page.");
|
|
@@ -222,17 +327,12 @@ function cmdLive(args) {
|
|
|
222
327
|
if (llmWired) {
|
|
223
328
|
log(" 3. LLM suggestions are ON — the relay runs the agent CLI per click,");
|
|
224
329
|
log(" so you never have to run /refine live.");
|
|
225
|
-
log(
|
|
226
|
-
log(" run `cursor-agent` once to log in, or set CURSOR_API_KEY.");
|
|
227
|
-
} else if (wantLlm) {
|
|
228
|
-
log(" 3. LLM was requested but cursor-agent isn't available (install failed).");
|
|
229
|
-
log(" Install it manually, then re-run:");
|
|
230
|
-
log(" curl https://cursor.com/install -fsS | bash");
|
|
231
|
-
log(" …or run /refine live in your editor for the in-IDE-agent path.");
|
|
330
|
+
if (authHint) log(` One-time: make sure the CLI is authenticated — ${authHint}.`);
|
|
232
331
|
} else {
|
|
233
|
-
log(" 3.
|
|
234
|
-
log("
|
|
235
|
-
log(" to
|
|
332
|
+
log(" 3. No agent CLI wired, so LLM features need a live answerer. Either:");
|
|
333
|
+
log(" • run /refine live in your editor (Cursor / Claude Code / Codex), or");
|
|
334
|
+
log(" • re-run with --llm to install the Cursor CLI, or");
|
|
335
|
+
log(" • export REFINE_AGENT_CMD='<your agent CLI>' and re-run.");
|
|
236
336
|
}
|
|
237
337
|
log("");
|
|
238
338
|
log("Press Ctrl-C to stop the relay and remove the injected tag.");
|
|
@@ -274,12 +374,21 @@ function main() {
|
|
|
274
374
|
if (cmd === "live") return cmdLive(args);
|
|
275
375
|
if (cmd === "stop") return cmdStop(args);
|
|
276
376
|
log("Refine — transitions.dev live tool");
|
|
277
|
-
log(" npx transitions-refine live
|
|
278
|
-
log(" npx transitions-refine live --
|
|
279
|
-
log(" npx transitions-refine
|
|
377
|
+
log(" npx transitions-refine live # inject panel + relay; auto-wire your agent CLI");
|
|
378
|
+
log(" npx transitions-refine live --agent claude # force an agent: cursor | claude | codex");
|
|
379
|
+
log(" npx transitions-refine live --llm # install the Cursor CLI if no agent is found");
|
|
380
|
+
log(" npx transitions-refine stop # remove the injected tag");
|
|
280
381
|
log("");
|
|
281
|
-
log("Options: --page <html> --port <n> --llm
|
|
382
|
+
log("Options: --page <html> --port <n> --agent <cursor|claude|codex> --llm");
|
|
383
|
+
log("It prefers the agent hosting this run (Cursor/Claude Code/Codex) so Refine uses");
|
|
384
|
+
log("the subscription you already have. Or set REFINE_AGENT_CMD to wire any CLI.");
|
|
282
385
|
process.exit(cmd ? 1 : 0);
|
|
283
386
|
}
|
|
284
387
|
|
|
285
|
-
|
|
388
|
+
// Run only when invoked as the CLI entry (npx / node bin/cli.mjs), so tests can
|
|
389
|
+
// import the resolver helpers without triggering a live run.
|
|
390
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
391
|
+
main();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { AGENTS, HOST_PRECEDENCE, detectHostAgent, resolveAgent, findBin };
|