transitions-refine 0.3.10 → 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.
@@ -1,11 +1,23 @@
1
1
  ---
2
2
  name: refine-live
3
- description: Become the live "Refine" agent for the Timeline Inspector. Use when the user runs `/refine live`, asks to "refine live", "go live", "answer refine jobs", or wants the timeline panel's Refine button (LLM mode), Accept button, or grouped scan to be backed by a real agent. Long-polls the local refine relay, reasons about each CSS transition with the transitions-dev skill, posts suggestions back to the browser panel, for "scan" jobs groups the page's transitions into components with open/close phases by reading the source, and for "apply" jobs writes the accepted timing changes into the user's source code.
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
- Turn yourself into the LLM behind the Timeline Inspector's **Refine** button. While
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 for the whole session
23
-
24
- **Keep polling continuously until the user explicitly stops you.** This is the
25
- only thing that keeps the panel's LLM tab "available", so do not give up on idle.
26
- A long stretch of `204` responses is *normal and expected* — it just means no one
27
- has clicked Refine yet. Re-poll immediately every time; never treat repeated
28
- `204`s as a reason to stop. The relay reports the agent as "available" for ~120s
29
- after your last poll, so as long as you keep looping you stay live and the user
30
- never has to re-run `/refine live`.
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. Immediately call it again.
39
- - HTTP `200` with JSON a job. Shape:
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
  {
@@ -197,9 +226,10 @@ never has to re-run `/refine live`.
197
226
  -H 'Content-Type: application/json' -d '{"message":"…"}'
198
227
  ```
199
228
 
200
- 5. **Go back to step 1.** Keep looping indefinitely. **Only stop when the user
201
- explicitly tells you to** (e.g. "stop refine", "exit live"). Do not stop just
202
- because it's been quiet idle is the normal state between clicks. If you do
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
203
233
  stop, tell them the LLM tab will go unavailable and how to restart
204
234
  (`/refine live`).
205
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. starts the local relay (which serves the panel at `/inject.js`).
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. Two ways:
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
- # A) persistent: install/wire the Cursor CLI so the relay answers LLM jobs itself,
42
- # per click, with no /refine live loop to keep alive (one-time CLI install)
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
- After `--llm`, make sure the CLI is authenticated once: run `cursor-agent` to log in, or set `CURSOR_API_KEY`.
47
+ # force a specific agent regardless of host
48
+ npx transitions-refine live --agent claude # cursor | claude | codex
47
49
 
48
- ```
49
- # B) in-IDE agent: run this in your editor to become the answerer yourself
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
- MIT
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 # inject the panel + start the relay
5
- // npx transitions-refine live --llm # + install/wire cursor-agent (persistent LLM)
6
- // npx transitions-refine stop # remove the injected <script> tag
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. ensures an LLM backend:
13
- // --llm → installs/wires the Cursor CLI (cursor-agent) so the relay
14
- // answers LLM jobs itself, persistently (no /refine live loop).
15
- // else → falls back to /refine live (in-IDE agent) + deterministic.
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 CLI (for the persistent LLM path) ──────────────────────────────────
115
- // The relay spawns `REFINE_AGENT_CMD` per job. We point it at cursor-agent so
116
- // LLM Refine works without a live `/refine live` loop. The binary may not be on
117
- // the non-interactive PATH, so we probe known install locations and use an
118
- // absolute path when wiring it up.
119
- const AGENT_BIN_CANDIDATES = [
120
- "cursor-agent",
121
- join(HOME, ".local/bin/cursor-agent"),
122
- join(HOME, ".cursor/bin/cursor-agent"),
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 findAgentBin() {
134
- return AGENT_BIN_CANDIDATES.find(isRunnable) || null;
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
- function installAgentCli() {
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 findAgentBin();
214
+ return findBin(AGENTS[0]);
151
215
  }
152
216
 
153
- // Returns an absolute-ish command string to put in REFINE_AGENT_CMD, or null.
154
- function ensureAgentCli({ autoInstall }) {
155
- const bin = findAgentBin();
156
- if (bin) return bin;
157
- if (!autoInstall) return null;
158
- return installAgentCli();
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) ensure an agent CLI so the relay can answer LLM jobs itself — this is
190
- // the persistent path (no `/refine live` loop to keep alive). Installing
191
- // fetches a system binary, so it only happens with explicit opt-in via
192
- // `--llm`. If REFINE_AGENT_CMD is already set we respect it as-is.
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
- let agentBin = null;
196
- if (process.env.REFINE_AGENT_CMD) {
197
- log(`✓ using REFINE_AGENT_CMD from environment: ${process.env.REFINE_AGENT_CMD}`);
198
- } else {
199
- agentBin = ensureAgentCli({ autoInstall: wantLlm });
200
- if (agentBin) {
201
- // Absolute path: the relay spawns via `sh -c`, whose PATH may not include
202
- // the CLI's install dir (e.g. ~/.local/bin). `-p` = headless print mode;
203
- // `--force` clears the workspace-trust / tool-approval prompts that would
204
- // otherwise hang a non-interactive spawn.
205
- const cmd = `${agentBin} -p --force`;
206
- env.REFINE_AGENT_CMD = cmd;
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(" One-time: make sure the CLI is authenticated —");
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. For persistent LLM (no /refine live needed), re-run with --llm");
234
- log(" (installs the Cursor CLI once). Or run /refine live in your editor");
235
- log(" to use the in-IDE agent.");
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 # inject panel + start relay");
278
- log(" npx transitions-refine live --llm # + install/wire cursor-agent for persistent LLM");
279
- log(" npx transitions-refine stop # remove the injected tag");
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 (enable persistent LLM via the Cursor CLI)");
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
- main();
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 };