leglas 0.2.0 → 0.3.0

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 CHANGED
@@ -52,7 +52,7 @@ and sessions that clean up after themselves.
52
52
  - Ask for changes without leaving the comparison: describe what you
53
53
  want on the direction you're looking at, and Leglas turns it into a
54
54
  precise request for your agent, file path included. Leave
55
- `leglas watch --run "claude -p {prompt}"` running in another terminal
55
+ `npx leglas watch --run "claude -p {prompt}"` running in another terminal
56
56
  and your agent picks each request up as you send it. In Claude Code,
57
57
  the Leglas MCP server can also push each request straight into your
58
58
  open session as a channel event (channels are a research preview:
@@ -76,57 +76,81 @@ preview of your app root. Add a config file to compare more than one thing.
76
76
 
77
77
  It works with whatever you're building in. Leglas never imports or
78
78
  executes your framework, so the target can be Next, Vite, Remix,
79
- SvelteKit, Astro, or a folder of static files. Requires Node 24 or
80
- newer.
79
+ SvelteKit, Astro, or a folder of static files.
80
+
81
+ ## Install
82
+
83
+ There is nothing you have to install: `npx leglas` fetches the CLI on
84
+ first use and starts from npm's cache after that, and every instruction
85
+ Leglas writes for agents uses the same form, so a fresh clone works
86
+ with no setup at all. Requires Node 24 or newer.
87
+
88
+ Two optional upgrades:
89
+
90
+ - `npm install -D leglas` pins the version in a project. Teammates and
91
+ CI get the same Leglas from their normal install, and `npx` resolves
92
+ the local copy from then on.
93
+ - `npm install -g leglas` is for typing `leglas` without the prefix.
81
94
 
82
95
  ## Working with coding agents
83
96
 
84
- Run `leglas init` once. It writes a section into your project's
85
- `AGENTS.md`, creates a starter config, and gitignores Leglas's working
86
- directory. That section travels with the repo, so Claude Code, Cursor,
87
- Codex, or whatever you switch to next opens the project already knowing
88
- how to add design directions to it. Every command accepts `--json` and
89
- prints a single machine-readable envelope, so agents drive the same CLI
90
- you do.
97
+ The fastest way in is the agent skill:
98
+
99
+ ```sh
100
+ npx skills add FredAmartey/leglas
101
+ ```
102
+
103
+ One install, and your agent recognises "give me a few directions for the
104
+ pricing page" as a Leglas exploration in any project, including ones
105
+ that have never seen Leglas. It sets the project up itself and gets to
106
+ work.
107
+
108
+ In a project, run `npx leglas init` once. It writes a section into your
109
+ project's `AGENTS.md`, creates a starter config, and gitignores Leglas's
110
+ working directory. That section travels with the repo, so Claude Code,
111
+ Cursor, Codex, or whatever you switch to next opens the project already
112
+ knowing how to add design directions to it. Every command accepts
113
+ `--json` and prints a single machine-readable envelope, so agents drive
114
+ the same CLI you do.
91
115
 
92
116
  The instructions center on one rule: add beside what exists, never
93
117
  rewrite it. Two directions that rewrite the same file cannot render from
94
118
  one server, and asking an agent to "make the hero calmer" tempts it to
95
119
  edit the hero. The supporting commands:
96
120
 
97
- - `leglas explore hero --count 6` briefs the exploration: what the set
121
+ - `npx leglas explore hero --count 6` briefs the exploration: what the set
98
122
  is for, why it only works if the six genuinely disagree, and how each
99
123
  direction registers. Unbriefed, six requests come back as six variants
100
124
  of one idea. With `--based-on "Aurora"` the goal flips: six deliberate
101
125
  variants of a direction you already like, and drifting into a new
102
126
  direction is the failure. The designs themselves are the agent's;
103
127
  Leglas prescribes none.
104
- - `leglas new hero --from src/Hero.tsx` scaffolds a switcher under
128
+ - `npx leglas new hero --from src/Hero.tsx` scaffolds a switcher under
105
129
  `.leglas/variants/hero/`. With `--from`, the baseline re-exports your
106
130
  real component, so you never compare against a stale copy. Leglas
107
131
  prints the one line to add in your component and does not edit it,
108
132
  because rewriting a file it does not understand is how a tool breaks a
109
133
  codebase. Scaffolded branch points return the fallback in production
110
134
  builds, so a committed one cannot expose an unreleased direction.
111
- - `leglas classify --change package.json --rewrite src/theme.css` answers
135
+ - `npx leglas classify --change package.json --rewrite src/theme.css` answers
112
136
  where a direction should live before it is written. Changing
113
137
  dependencies, build configuration, or an existing file's behaviour
114
138
  cannot be additive, so those directions build on their own git branch
115
139
  and register with `leglas add --branch`. Everything else stays in-app,
116
140
  where switching is instant.
117
- - `leglas show "Aurora" --json` answers for one direction: its entry, the
141
+ - `npx leglas show "Aurora" --json` answers for one direction: its entry, the
118
142
  source file behind it, the variants based on it, what it is being compared
119
143
  against, and anything still pending on it. Copying a direction from the
120
144
  rail hands over a block that ends in this command, so an agent given the
121
145
  block can go and get the rest.
122
- - `leglas keep "Aurora" --to src/components/hero.tsx` moves the winner
146
+ - `npx leglas keep "Aurora" --to src/components/hero.tsx` moves the winner
123
147
  into real source and ends the exploration.
124
148
 
125
149
  Asking for a change works from the interface too. Type what you want
126
150
  changed into the field under the rail (or press `R`) and Leglas composes a
127
151
  prompt naming the direction and the file behind it, copies it to your
128
152
  clipboard, and queues it. The direction it means is the one highlighted
129
- directly above the field. Your agent drains the queue with `leglas requests --json` and clears
153
+ directly above the field. Your agent drains the queue with `npx leglas requests --json` and clears
130
154
  it with `--clear`. Leglas runs no model of its own; your agent already
131
155
  knows your conventions and your taste.
132
156
 
@@ -136,6 +160,9 @@ For agent hosts that cannot run shell commands, `leglas-mcp` exposes the
136
160
  same operations as MCP tools over stdio: `start`, `add`, `list`, `show`,
137
161
  `classify`, `explore`, `scaffold`, `keep`, `requests`, and `init`. Each
138
162
  tool calls exactly what the CLI calls and returns the same envelope.
163
+ `watch` is the one command with no tool behind it: it is a loop that
164
+ holds a terminal open, and on a host that speaks channels the server
165
+ already pushes each request into the session as it arrives.
139
166
 
140
167
  ```sh
141
168
  claude mcp add leglas -- npx -y leglas-mcp
@@ -147,9 +174,34 @@ Or in `.mcp.json`:
147
174
  { "mcpServers": { "leglas": { "command": "npx", "args": ["-y", "leglas-mcp"] } } }
148
175
  ```
149
176
 
150
- The host's working directory names the project. The `start` tool boots
151
- the viewer and returns its URL, and anything it started stops when the
152
- session ends.
177
+ The host's working directory names the project, the same contract as the
178
+ CLI. A host that starts the server somewhere else is asked where the
179
+ project is, over MCP roots. The `start` tool boots the viewer and returns
180
+ its URL, and anything it started stops when the session ends.
181
+
182
+ ### As an Agent Plugin
183
+
184
+ The repository is also an [Agent Plugin](https://agent-plugins.org), the
185
+ open standard for shipping Agent Skills and MCP server configuration in
186
+ one format. Clients that implement it install the skill and the server
187
+ together, instead of the two steps above. It is a layout rather than a
188
+ build: `plugin.json` and `mcp.json` at the root, the skill in
189
+ `skills/leglas/`, nothing generated.
190
+
191
+ An Agent Plugins client starts a plugin's server in the plugin's own
192
+ install directory rather than the project, so on that path the working
193
+ directory names a copy of Leglas and nothing else. The server therefore
194
+ takes the project from the workspace the host declares over MCP roots,
195
+ and the working directory only when it sits inside one. If a host offers
196
+ neither, `LEGLAS_PROJECT_DIR` names the project outright; without it the
197
+ tools report that there is no project rather than writing into a plugin
198
+ cache. `mcp.json` passes `${PLUGIN_ROOT}` for exactly that check, and
199
+ nothing else.
200
+
201
+ The plugin's version covers the skill and the configuration, not the
202
+ server it launches: `npx` fetches the current `leglas-mcp` the same way
203
+ every `npx leglas` in these instructions fetches the current CLI, which
204
+ keeps both faces of Leglas on one version in a project they share.
153
205
 
154
206
  ## Configuration
155
207
 
package/dist/bin.js CHANGED
@@ -51,7 +51,7 @@ function parseNew(rest) {
51
51
  if (surface === void 0) {
52
52
  return {
53
53
  kind: "error",
54
- message: "leglas new needs a surface name, for example: leglas new hero"
54
+ message: "leglas new needs a surface name, for example: npx leglas new hero"
55
55
  };
56
56
  }
57
57
  return { kind: "new", surface, print, json, from };
@@ -134,7 +134,7 @@ function parseClassify(rest) {
134
134
  if (changes.length === 0) {
135
135
  return {
136
136
  kind: "error",
137
- message: "leglas classify needs what the direction will touch, for example: leglas classify --change package.json --rewrite src/theme.css"
137
+ message: "leglas classify needs what the direction will touch, for example: npx leglas classify --change package.json --rewrite src/theme.css"
138
138
  };
139
139
  }
140
140
  return { kind: "classify", changes, json };
@@ -209,7 +209,7 @@ function parseArgs(argv) {
209
209
  if (title === void 0) {
210
210
  return {
211
211
  kind: "error",
212
- message: 'leglas keep needs a direction title, for example: leglas keep "Aurora" --to src/components/hero.tsx'
212
+ message: 'leglas keep needs a direction title, for example: npx leglas keep "Aurora" --to src/components/hero.tsx'
213
213
  };
214
214
  }
215
215
  if (to === void 0) {
@@ -259,7 +259,7 @@ function parseArgs(argv) {
259
259
  if (surface === void 0) {
260
260
  return {
261
261
  kind: "error",
262
- message: "leglas explore needs a surface name, for example: leglas explore hero --count 6"
262
+ message: "leglas explore needs a surface name, for example: npx leglas explore hero --count 6"
263
263
  };
264
264
  }
265
265
  return { kind: "explore", surface, count, basedOn, json };
@@ -300,7 +300,7 @@ function parseArgs(argv) {
300
300
  if (title === void 0) {
301
301
  return {
302
302
  kind: "error",
303
- message: 'leglas show needs a direction title, for example: leglas show "Aurora" --json'
303
+ message: 'leglas show needs a direction title, for example: npx leglas show "Aurora" --json'
304
304
  };
305
305
  }
306
306
  return { kind: "show", title, json };
@@ -522,12 +522,12 @@ function isExplorationFile(path) {
522
522
  }
523
523
  var CHECKOUT_STEPS = [
524
524
  "Build the direction on its own branch: git switch -c <branch>, commit it there, switch back.",
525
- 'Register it: leglas add --title "<title>" --url "/" --branch <branch>.',
525
+ 'Register it: npx leglas add --title "<title>" --url "/" --branch <branch>.',
526
526
  "Make sure the config sets devCommand (with {port}), so Leglas can start the checkout."
527
527
  ];
528
528
  var IN_APP_STEPS = [
529
529
  "Author it additively under .leglas/variants/<surface>/, beside the existing directions.",
530
- 'Register it: leglas add --title "<title>" --url "/?v-<surface>=<direction>".'
530
+ 'Register it: npx leglas add --title "<title>" --url "/?v-<surface>=<direction>".'
531
531
  ];
532
532
  function classifyDirection(input) {
533
533
  const checkout = (reason) => ({ level: "checkout", reason, steps: CHECKOUT_STEPS });
@@ -983,7 +983,12 @@ async function removeRequest(cwd, id) {
983
983
  return true;
984
984
  }
985
985
  async function clearRequests(cwd) {
986
- await writeQueue(cwd, []);
986
+ const requests = await readRequests(cwd);
987
+ const pending = requests.filter((request) => request.status !== "picked-up");
988
+ const cleared = requests.length - pending.length;
989
+ if (cleared > 0)
990
+ await writeQueue(cwd, pending);
991
+ return { cleared, pending: pending.length };
987
992
  }
988
993
 
989
994
  // ../server/dist/renames.js
@@ -1022,7 +1027,7 @@ function resolveTitle(input, titles, renames) {
1022
1027
  import { createReadStream, existsSync as existsSync2, statSync } from "fs";
1023
1028
  import http2 from "http";
1024
1029
  import net3 from "net";
1025
- import { extname, join as join6, normalize } from "path";
1030
+ import { extname, join as join6, normalize, relative as relative2 } from "path";
1026
1031
  var LEGLAS_PREFIX = "/leglas";
1027
1032
  var DEFAULT_PORT = 4100;
1028
1033
  var PORT_ATTEMPTS = 20;
@@ -1074,8 +1079,8 @@ function probe(target, timeoutMs = 1e3) {
1074
1079
  });
1075
1080
  }
1076
1081
  function serveFrom(res, dir, relativePath) {
1077
- const relative3 = normalize(relativePath).replace(/^(\.\.[/\\])+/, "");
1078
- const candidate = join6(dir, relative3);
1082
+ const relative4 = normalize(relativePath).replace(/^(\.\.[/\\])+/, "");
1083
+ const candidate = join6(dir, relative4);
1079
1084
  if (!candidate.startsWith(dir))
1080
1085
  return false;
1081
1086
  if (!existsSync2(candidate) || !statSync(candidate).isFile())
@@ -1088,9 +1093,36 @@ function serveFrom(res, dir, relativePath) {
1088
1093
  return true;
1089
1094
  }
1090
1095
  function serveShellFile(res, shellDir, urlPath) {
1091
- const relative3 = normalize(urlPath.slice(LEGLAS_PREFIX.length)).replace(/^(\.\.[/\\])+/, "");
1092
- const isRoot = relative3 === "" || relative3 === "." || relative3 === "/";
1093
- return serveFrom(res, shellDir, isRoot ? "index.html" : relative3);
1096
+ const relative4 = normalize(urlPath.slice(LEGLAS_PREFIX.length)).replace(/^(\.\.[/\\])+/, "");
1097
+ const isRoot = relative4 === "" || relative4 === "." || relative4 === "/";
1098
+ return serveFrom(res, shellDir, isRoot ? "index.html" : relative4);
1099
+ }
1100
+ function snapshotConfig(cwd) {
1101
+ const path = findConfigFile(cwd);
1102
+ if (path === null)
1103
+ return null;
1104
+ try {
1105
+ return { path, mtimeMs: statSync(path).mtimeMs };
1106
+ } catch {
1107
+ return null;
1108
+ }
1109
+ }
1110
+ function configStalenessNotice(cwd, boot, current) {
1111
+ if (boot === null && current === null)
1112
+ return null;
1113
+ if (boot === null && current !== null) {
1114
+ const label = relative2(cwd, current.path) || current.path;
1115
+ return `${label} appeared after Leglas started. Restart leglas to pick it up.`;
1116
+ }
1117
+ if (boot !== null && current === null) {
1118
+ const label = relative2(cwd, boot.path) || boot.path;
1119
+ return `${label} was removed after Leglas started. Restart leglas to run without it.`;
1120
+ }
1121
+ if (boot !== null && current !== null && (boot.path !== current.path || boot.mtimeMs !== current.mtimeMs)) {
1122
+ const label = relative2(cwd, current.path) || current.path;
1123
+ return `${label} changed after Leglas started. Restart leglas to pick it up.`;
1124
+ }
1125
+ return null;
1094
1126
  }
1095
1127
  var PLACEHOLDER = `<!doctype html>
1096
1128
  <meta charset="utf-8">
@@ -1136,12 +1168,17 @@ async function startServer(options) {
1136
1168
  const { config, configErrors = [], shellDir = null, project = "", cwd = process.cwd(), fileMounts = /* @__PURE__ */ new Map() } = options;
1137
1169
  const target = config?.devServer ?? "http://localhost:3000";
1138
1170
  const proxy = createProxyHandler({ target });
1171
+ const bootConfigSnapshot = snapshotConfig(cwd);
1139
1172
  let lastSeen = null;
1140
1173
  const server = http2.createServer((req, res) => {
1141
1174
  const url = req.url ?? "/";
1142
1175
  const path = url.split("?")[0] ?? "/";
1143
1176
  if (path === `${LEGLAS_PREFIX}/api/config`) {
1144
1177
  const boot = config?.previews ?? [];
1178
+ const errors = [...configErrors];
1179
+ const notice = configStalenessNotice(cwd, bootConfigSnapshot, snapshotConfig(cwd));
1180
+ if (notice !== null)
1181
+ errors.push(notice);
1145
1182
  return void readLocalPreviews(cwd).then(({ previews: local }) => {
1146
1183
  const known = new Set(boot.map((preview) => preview.title));
1147
1184
  const fresh = local.filter((preview) => !known.has(preview.title) && preview.branch === void 0 && preview.file === void 0);
@@ -1149,13 +1186,13 @@ async function startServer(options) {
1149
1186
  project,
1150
1187
  devServer: target,
1151
1188
  previews: [...boot, ...fresh],
1152
- errors: configErrors
1189
+ errors
1153
1190
  });
1154
1191
  }).catch(() => sendJson(res, 200, {
1155
1192
  project,
1156
1193
  devServer: target,
1157
1194
  previews: boot,
1158
- errors: configErrors
1195
+ errors
1159
1196
  }));
1160
1197
  }
1161
1198
  if (path === `${LEGLAS_PREFIX}/api/request` && req.method === "POST") {
@@ -1229,21 +1266,28 @@ async function startServer(options) {
1229
1266
  const rest = path.slice(FILES_PREFIX.length + 1);
1230
1267
  const slash = rest.indexOf("/");
1231
1268
  const slug = slash === -1 ? rest : rest.slice(0, slash);
1232
- let relative3 = slash === -1 ? "" : rest.slice(slash + 1);
1269
+ let relative4 = slash === -1 ? "" : rest.slice(slash + 1);
1233
1270
  try {
1234
- relative3 = decodeURIComponent(relative3);
1271
+ relative4 = decodeURIComponent(relative4);
1235
1272
  } catch {
1236
- relative3 = "";
1273
+ relative4 = "";
1237
1274
  }
1238
1275
  const dir = fileMounts.get(slug);
1239
- if (dir !== void 0 && relative3 !== "" && serveFrom(res, dir, relative3))
1276
+ if (dir !== void 0 && relative4 !== "" && serveFrom(res, dir, relative4))
1240
1277
  return;
1241
1278
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
1242
1279
  return res.end("Leglas: no such preview file.");
1243
1280
  }
1281
+ if (path.startsWith(`${LEGLAS_PREFIX}/api/`)) {
1282
+ return sendJson(res, 404, { error: "No such Leglas API path." });
1283
+ }
1244
1284
  if (path === LEGLAS_PREFIX || path.startsWith(`${LEGLAS_PREFIX}/`)) {
1245
1285
  if (shellDir !== null && serveShellFile(res, shellDir, path))
1246
1286
  return;
1287
+ if (shellDir !== null) {
1288
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" });
1289
+ return res.end("Leglas: no such path.");
1290
+ }
1247
1291
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
1248
1292
  return res.end(PLACEHOLDER);
1249
1293
  }
@@ -1515,8 +1559,8 @@ The set exists to be chosen from, and the choice only means something if the dir
1515
1559
  One trap, seen every time this goes wrong: a set collapses toward whichever direction is built first. Decide all ${count} before building any, and if two would read as the same direction at a glance, replace one of them.` : `Build ${count} variations of the "${basedOn}" direction for "${surface}".
1516
1560
 
1517
1561
  The set exists to pick a variant of a direction already chosen, so every variation must stay recognisably that direction. The trap here is drift: change enough and the comparison stops being about the variant. Vary each one deliberately and hold everything else still; if a variation grows into a new direction, it belongs in its own exploration instead.`;
1518
- const register = basedOn === null ? ` leglas add --title "<name>" --url "/?v-${slug}=<key>" --note "<the idea, one line>"` : ` leglas add --title "<name>" --url "/?v-${slug}=<key>" --based-on ${JSON.stringify(basedOn)} --note "<the idea, one line>"`;
1519
- const mechanics = `Each one is its own file under .leglas/variants/${slug}/, listed in the DIRECTIONS map in that folder's switch file. If there is no switch file yet, run \`leglas new ${slug}\` first. Register each one the moment it renders, not the set at the end. The interface picks a registration up within seconds, so whoever asked watches the set fill in:
1562
+ const register = basedOn === null ? ` npx leglas add --title "<name>" --url "/?v-${slug}=<key>" --note "<the idea, one line>"` : ` npx leglas add --title "<name>" --url "/?v-${slug}=<key>" --based-on ${JSON.stringify(basedOn)} --note "<the idea, one line>"`;
1563
+ const mechanics = `Each one is its own file under .leglas/variants/${slug}/, listed in the DIRECTIONS map in that folder's switch file. If there is no switch file yet, run \`npx leglas new ${slug}\` first. Register each one the moment it renders, not the set at the end. The interface picks a registration up within seconds, so whoever asked watches the set fill in:
1520
1564
 
1521
1565
  ${register}
1522
1566
 
@@ -1572,46 +1616,48 @@ When asked for design variations, alternatives, or "a few options":
1572
1616
  If a direction cannot be additive, because it must change dependencies,
1573
1617
  change build configuration, or rewrite an existing file's behaviour, ask
1574
1618
  where it should live before writing it:
1575
- \`leglas classify --change package.json --rewrite src/theme.css --json\`.
1619
+ \`npx leglas classify --change package.json --rewrite src/theme.css --json\`.
1576
1620
  When the answer is \`checkout\`, build the direction on its own git branch
1577
- and register it with \`leglas add --title "\u2026" --url "/" --branch <branch>\`
1621
+ and register it with \`npx leglas add --title "\u2026" --url "/" --branch <branch>\`
1578
1622
  (the config needs \`devCommand\` with \`{port}\`). Everything below is the
1579
1623
  ordinary, in-app path.
1580
- 2. Run \`leglas explore <surface> --count <n>\` first, adding
1624
+ 2. Run \`npx leglas explore <surface> --count <n>\` first, adding
1581
1625
  \`--based-on "<title>"\` when the user wants variations of a direction they
1582
1626
  already like. It prints what the set needs and how to register it. In
1583
1627
  short: new directions must genuinely disagree with each other, variants of
1584
1628
  one must not, and either way decide the whole set before building any of
1585
1629
  it. The designs themselves are yours.
1586
1630
  3. If the surface has no switcher yet, run
1587
- \`leglas new <surface> --from <the component that renders it today>\`. It
1631
+ \`npx leglas new <surface> --from <the component that renders it today>\`. It
1588
1632
  writes one under \`.leglas/variants/<surface>/\` and prints the single line
1589
1633
  to add where that surface renders. \`--from\` makes the baseline re-export
1590
1634
  the real component rather than copying it, so it stays live.
1591
- 4. Before building, make sure the interface is up. If \`leglas\` is not
1592
- already running, tell the user to run it, and hand them the URL now
1593
- rather than when the set is done: the rail picks up each registration
1594
- within seconds, so they get to watch the exploration fill in.
1635
+ 4. Before building, make sure the interface is up. If Leglas is not
1636
+ already running, tell the user to run \`npx leglas\`, and hand them the
1637
+ URL now rather than when the set is done: the rail picks up each
1638
+ registration within seconds, so they get to watch the exploration fill in.
1595
1639
  5. Build one direction at a time: its own file beside the others in
1596
1640
  \`.leglas/variants/<surface>/\`, listed in the \`DIRECTIONS\` map in that
1597
1641
  folder's \`switch\` file, then registered the moment it renders:
1598
- \`leglas add --title "Aurora" --url "/?v-<surface>=aurora" --note "One line on the idea."\`
1642
+ \`npx leglas add --title "Aurora" --url "/?v-<surface>=aurora" --note "One line on the idea."\`
1599
1643
  Register each direction as it lands, never the whole set at the end. To
1600
1644
  the user watching the rail, a batch at the end is minutes of nothing and
1601
1645
  then everything at once.
1602
1646
 
1603
- When the user asks to change one direction, check \`leglas requests --json\`
1647
+ When the user asks to change one direction, check \`npx leglas requests --json\`
1604
1648
  first: they may have described it from the interface, and the request names the
1605
- exact file. Clear the queue with \`leglas requests --clear\` once done.
1649
+ exact file. Acknowledge them with \`npx leglas requests --clear\` once done: it
1650
+ drops what you collected and reports anything the user typed while you worked,
1651
+ which is yours to collect and do next.
1606
1652
 
1607
1653
  If the user wants requests handled the moment they are typed, without relaying
1608
- each one, tell them about \`leglas watch --run "claude -p {prompt}"\` (any
1654
+ each one, tell them about \`npx leglas watch --run "claude -p {prompt}"\` (any
1609
1655
  agent command works; {prompt} receives the request). It runs in their
1610
1656
  terminal, hands each request to that command as it arrives, and the interface
1611
1657
  shows the request's progress.
1612
1658
 
1613
1659
  When the user picks a winner, run
1614
- \`leglas keep "<title>" --to <path in real source>\`. It moves that direction
1660
+ \`npx leglas keep "<title>" --to <path in real source>\`. It moves that direction
1615
1661
  out of the ignored directory, deletes the rest of the exploration, and drops
1616
1662
  them from the rail. Then change their component to use the kept component
1617
1663
  instead of the switcher.
@@ -1622,14 +1668,14 @@ Useful to know:
1622
1668
  ships. Move a direction into real source only when it wins.
1623
1669
  - If the project has no running app yet, a direction can be a plain HTML
1624
1670
  file: write it under \`.leglas/pages/\` and register it with
1625
- \`leglas add --title "Aurora" --file .leglas/pages/aurora.html\`. Leglas
1671
+ \`npx leglas add --title "Aurora" --file .leglas/pages/aurora.html\`. Leglas
1626
1672
  serves the file itself, so no dev server is needed. Sibling assets in the
1627
1673
  same directory resolve normally.
1628
1674
  - Titles identify previews and must be unique. The user may rename one in the
1629
1675
  rail, which renames it on their machine only; the commands answer to either
1630
1676
  name, so use whichever they said.
1631
- - \`leglas list\` shows every direction, shared and local.
1632
- - \`leglas show "<title>" --json\` answers for one of them: the file behind it,
1677
+ - \`npx leglas list\` shows every direction, shared and local.
1678
+ - \`npx leglas show "<title>" --json\` answers for one of them: the file behind it,
1633
1679
  the variants based on it, what it is being compared against, and anything
1634
1680
  they have asked for that is not done yet. Run it when handed a direction you
1635
1681
  did not register yourself.
@@ -1639,7 +1685,7 @@ Useful to know:
1639
1685
  ${AGENTS_MARKER_END}
1640
1686
  `;
1641
1687
  var STARTER_CONFIG = `// Previews are URLs of your own app. Add one per direction you want to
1642
- // compare, then run \`leglas\`.
1688
+ // compare, then run \`npx leglas\`.
1643
1689
  export default {
1644
1690
  // Where your dev server is. Override at the command line with --user-port.
1645
1691
  devServer: "http://localhost:3000",
@@ -1737,7 +1783,7 @@ function planKeep(options) {
1737
1783
  if (!winner) {
1738
1784
  return {
1739
1785
  ok: false,
1740
- error: `No direction called ${JSON.stringify(options.title)}. Run leglas list to see them.`
1786
+ error: `No direction called ${JSON.stringify(options.title)}. Run npx leglas list to see them.`
1741
1787
  };
1742
1788
  }
1743
1789
  const from = targetFor(winner.url);
@@ -1785,7 +1831,7 @@ function resolveOrExplain(input, titles, renames) {
1785
1831
  }
1786
1832
  return {
1787
1833
  ok: false,
1788
- error: `No direction called ${JSON.stringify(input)}. Renaming one in the rail only renames it here, and it still answers to its title in the config, which its reference block quotes. leglas list shows every title.`
1834
+ error: `No direction called ${JSON.stringify(input)}. Renaming one in the rail only renames it here, and it still answers to its title in the config, which its reference block quotes. npx leglas list shows every title.`
1789
1835
  };
1790
1836
  }
1791
1837
 
@@ -1935,7 +1981,7 @@ async function runNew(options, deps) {
1935
1981
  deps.log("Then register them so they appear in the interface:");
1936
1982
  deps.log("");
1937
1983
  for (const preview of plan.previews) {
1938
- deps.log(` leglas add --title ${JSON.stringify(preview.title)} --url ${JSON.stringify(preview.url)}`);
1984
+ deps.log(` npx leglas add --title ${JSON.stringify(preview.title)} --url ${JSON.stringify(preview.url)}`);
1939
1985
  }
1940
1986
  return { exitCode: 0, written };
1941
1987
  }
@@ -1964,7 +2010,7 @@ async function runAdd(options, deps) {
1964
2010
  const local = await readLocalPreviews(options.cwd);
1965
2011
  const titles = new Set([...shared, ...local.previews].map((preview) => preview.title));
1966
2012
  if (!titles.has(options.preview.basedOn)) {
1967
- const error = `--based-on names ${JSON.stringify(options.preview.basedOn)}, which is not a registered direction. leglas list shows what exists.`;
2013
+ const error = `--based-on names ${JSON.stringify(options.preview.basedOn)}, which is not a registered direction. npx leglas list shows what exists.`;
1968
2014
  if (options.json) envelope(deps, false, { error });
1969
2015
  else deps.error(error);
1970
2016
  return { exitCode: 1 };
@@ -1997,6 +2043,7 @@ async function runAdd(options, deps) {
1997
2043
  local: true,
1998
2044
  ...options.preview.branch === void 0 ? {} : { branch: options.preview.branch },
1999
2045
  ...options.preview.file === void 0 ? {} : { file: options.preview.file },
2046
+ note: options.preview.branch === void 0 && options.preview.file === void 0 ? "A running interface picks this up within seconds." : "Restart Leglas to see this preview: branch checkouts and file mounts are built when Leglas starts.",
2000
2047
  ...needsDevCommand ? { warning: "The config sets no devCommand, so Leglas cannot start this branch yet. Add devCommand (with {port}) to the config." } : {}
2001
2048
  });
2002
2049
  } else {
@@ -2012,7 +2059,7 @@ async function runAdd(options, deps) {
2012
2059
  if (options.preview.branch === void 0 && options.preview.file === void 0) {
2013
2060
  deps.log("Local to this machine. A running interface picks it up within seconds.");
2014
2061
  } else {
2015
- deps.log("Local to this machine. Restart Leglas to see it, or run leglas list.");
2062
+ deps.log("Local to this machine. Restart Leglas to see it, or run npx leglas list.");
2016
2063
  }
2017
2064
  }
2018
2065
  return { exitCode: 0 };
@@ -2047,7 +2094,7 @@ async function runList(options, deps) {
2047
2094
  return { exitCode: errors.length === 0 ? 0 : 1 };
2048
2095
  }
2049
2096
  if (previews.length === 0) {
2050
- deps.log("No previews yet. Add one with leglas add, or list them in leglas.config.ts.");
2097
+ deps.log("No previews yet. Add one with npx leglas add, or list them in leglas.config.ts.");
2051
2098
  } else {
2052
2099
  const width = Math.max(...previews.map((preview) => preview.title.length));
2053
2100
  for (const preview of previews) {
@@ -2062,9 +2109,16 @@ async function runList(options, deps) {
2062
2109
  }
2063
2110
  async function runRequests(options, deps) {
2064
2111
  if (options.clear) {
2065
- await clearRequests(options.cwd);
2066
- if (options.json) envelope(deps, true, { cleared: true });
2067
- else deps.log("Queue cleared.");
2112
+ const { cleared, pending } = await clearRequests(options.cwd);
2113
+ if (options.json) envelope(deps, true, { cleared, pending });
2114
+ else {
2115
+ deps.log(cleared === 1 ? "Cleared 1 request." : `Cleared ${cleared} requests.`);
2116
+ if (pending > 0) {
2117
+ deps.log(
2118
+ `${pending} arrived while you worked. Run npx leglas requests to collect ${pending === 1 ? "it" : "them"}.`
2119
+ );
2120
+ }
2121
+ }
2068
2122
  return { exitCode: 0 };
2069
2123
  }
2070
2124
  const requests = await collectRequests(options.cwd);
@@ -2081,7 +2135,7 @@ async function runRequests(options, deps) {
2081
2135
  if (request.target !== null) deps.log(` ${request.target}`);
2082
2136
  }
2083
2137
  deps.log("");
2084
- deps.log("Run leglas requests --json to get the full prompts, then --clear when done.");
2138
+ deps.log("Run npx leglas requests --json to get the full prompts, then --clear when done.");
2085
2139
  return { exitCode: 0 };
2086
2140
  }
2087
2141
 
@@ -2107,7 +2161,7 @@ function planShow({ title, previews, requests }) {
2107
2161
  if (!found) {
2108
2162
  return {
2109
2163
  ok: false,
2110
- error: `No direction called ${JSON.stringify(title)}. Run leglas list to see them.`
2164
+ error: `No direction called ${JSON.stringify(title)}. Run npx leglas list to see them.`
2111
2165
  };
2112
2166
  }
2113
2167
  const variants = previews.filter((preview) => preview.basedOn === title).map(describe);
@@ -2186,7 +2240,7 @@ async function runShow(options, deps) {
2186
2240
  deps.log(` Pending, not yet done (${plan.requests.length}):`);
2187
2241
  for (const request of plan.requests) deps.log(` ${request.status} ${request.intent}`);
2188
2242
  deps.log("");
2189
- deps.log(" Run leglas requests --json for the full prompts.");
2243
+ deps.log(" Run npx leglas requests --json for the full prompts.");
2190
2244
  }
2191
2245
  return { exitCode: 0 };
2192
2246
  }
@@ -2199,7 +2253,7 @@ import { dirname as dirname7, join as join12 } from "path";
2199
2253
  // src/watch.ts
2200
2254
  var WATCH_PATH = ".leglas/watch.json";
2201
2255
  var PROMPT_TOKEN = "{prompt}";
2202
- var EXAMPLE = `leglas watch --run "claude -p ${PROMPT_TOKEN}"`;
2256
+ var EXAMPLE = `npx leglas watch --run "claude -p ${PROMPT_TOKEN}"`;
2203
2257
  function tokenize(template) {
2204
2258
  const tokens = [];
2205
2259
  let current = "";
@@ -2311,7 +2365,7 @@ async function runWatch(options, deps) {
2311
2365
  const raw = options.run ?? saved;
2312
2366
  if (raw === null) {
2313
2367
  deps.error(
2314
- 'Watch needs an agent command the first time: leglas watch --run "claude -p {prompt}"'
2368
+ 'Watch needs an agent command the first time: npx leglas watch --run "claude -p {prompt}"'
2315
2369
  );
2316
2370
  return { exitCode: 1 };
2317
2371
  }
@@ -2398,7 +2452,7 @@ async function runWatch(options, deps) {
2398
2452
  // src/run.ts
2399
2453
  import { existsSync as existsSync5 } from "fs";
2400
2454
  import { createRequire } from "module";
2401
- import { basename as basename3, dirname as dirname8, join as join13, relative as relative2 } from "path";
2455
+ import { basename as basename3, dirname as dirname8, join as join13, relative as relative3 } from "path";
2402
2456
  import { fileURLToPath } from "url";
2403
2457
  function findShellDir() {
2404
2458
  const bundled = join13(dirname8(fileURLToPath(import.meta.url)), "shell");
@@ -2511,7 +2565,7 @@ async function run2(options, deps) {
2511
2565
  })
2512
2566
  );
2513
2567
  } else {
2514
- const configLabel = loaded.path === null ? "no config file, previewing the app root" : relative2(options.cwd, loaded.path) || loaded.path;
2568
+ const configLabel = loaded.path === null ? "no config file, previewing the app root" : relative3(options.cwd, loaded.path) || loaded.path;
2515
2569
  deps.log(`Leglas ${url}`);
2516
2570
  deps.log(
2517
2571
  `app ${devServer}${app !== null ? " (started by Leglas)" : health.reachable ? "" : " (not reachable)"}`