@usecanary/cli 0.2.1 → 0.4.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
@@ -108,7 +108,8 @@ await saveScreenshot(await page.screenshot(), "signed-in.png"); // saveScreens
108
108
  - **Files** (sandboxed to `~/.canary/tmp/`) — `saveScreenshot(buffer, name)`, `writeFile(name, data)`,
109
109
  `readFile(name)` to pass values between steps.
110
110
 
111
- The engine documents the same API — run `canary-browser --help`, or read the
111
+ The full reference is built into this CLI — run `canary --help` or `canary run --help`
112
+ (the engine's `canary-browser --help` documents the same API), or read the
112
113
  [canary-scripting reference](https://github.com/usecanary/canary/blob/main/skills/canary-scripting/references/REFERENCE.md).
113
114
 
114
115
  ## Artifacts
package/dist/cli.cjs CHANGED
@@ -7462,15 +7462,15 @@ var require_help = __commonJS({
7462
7462
  * @return {string}
7463
7463
  *
7464
7464
  */
7465
- wrap(str, width, indent, minColumnWidth = 40) {
7465
+ wrap(str, width, indent2, minColumnWidth = 40) {
7466
7466
  const indents = " \\f\\t\\v\xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF";
7467
7467
  const manualIndent = new RegExp(`[\\n][${indents}]+`);
7468
7468
  if (str.match(manualIndent)) return str;
7469
- const columnWidth = width - indent;
7469
+ const columnWidth = width - indent2;
7470
7470
  if (columnWidth < minColumnWidth) return str;
7471
- const leadingStr = str.slice(0, indent);
7472
- const columnText = str.slice(indent).replace("\r\n", "\n");
7473
- const indentString = " ".repeat(indent);
7471
+ const leadingStr = str.slice(0, indent2);
7472
+ const columnText = str.slice(indent2).replace("\r\n", "\n");
7473
+ const indentString = " ".repeat(indent2);
7474
7474
  const zeroWidthSpace = "\u200B";
7475
7475
  const breaks = `\\s${zeroWidthSpace}`;
7476
7476
  const regex = new RegExp(
@@ -9967,6 +9967,181 @@ function createLogger(opts = {}) {
9967
9967
 
9968
9968
  // ../../packages/cli-kit/src/index.ts
9969
9969
  var import_pino_pretty = __toESM(require_pino_pretty(), 1);
9970
+
9971
+ // ../../packages/cli-kit/src/snippets.generated.ts
9972
+ var API_BROWSER = "- `browser.getPage(nameOrId)` \u2014 get-or-create a named page, or attach to an existing tab by the\n `id` from `listPages()`. Named pages persist across steps in a session \u2014 call with the same\n name to reuse the tab.\n- `browser.newPage()` \u2014 an anonymous page, auto-closed when the script ends; does not persist.\n- `browser.listPages()` \u2014 list every open tab: `[{ id, url, title, name }]` (`name` is `null`\n for tabs you never named).\n- `browser.closePage(name)` \u2014 close and forget a named page.";
9973
+ var API_CONSOLE = "- `console.log` / `console.info` write to stdout; `console.warn` / `console.error` write to\n stderr. Top-level `console.log` is your script's output channel.\n- `console.log` inside `page.evaluate(() => \u2026)` runs in the page and is captured into the\n session's console artifact instead.";
9974
+ var API_FILE_HELPERS = 'All file I/O is async (await it), sandboxed to `~/.canary/tmp/` (no filesystem escape), and\nreturns the full path to the file:\n\n- `saveScreenshot(buffer, name)` \u2014 persist a screenshot buffer; buffer first:\n `const path = await saveScreenshot(await page.screenshot(), "home.png");`\n- `writeFile(name, data)` \u2014 write a small file (e.g. JSON state):\n `await writeFile("results.json", JSON.stringify(data));`\n- `readFile(name)` \u2014 read it back (returns the contents as a string):\n `const data = JSON.parse(await readFile("results.json"));`';
9975
+ var API_GLOBALS = "Every script gets these globals:\n\n- `browser` \u2014 pre-connected browser handle (see the script API)\n- `console` \u2014 `log` / `info` / `warn` / `error`, captured per run\n- `setTimeout` / `clearTimeout` \u2014 basic timers\n- `saveScreenshot(buffer, name)` \u2014 save a screenshot buffer (async \u2014 await it)\n- `writeFile(name, data)` / `readFile(name)` \u2014 small-file persistence (async \u2014 await them)";
9976
+ var API_PLAYWRIGHT_METHODS = '- `page.goto(url, { waitUntil: "domcontentloaded" })` \u2014 navigate; `waitUntil` is `"load"` /\n `"domcontentloaded"` / `"networkidle"` (prefer `"domcontentloaded"` on dev servers)\n- `page.title()` / `page.url()` \u2014 current title / URL\n- `page.snapshotForAI(options)` \u2014 AI-optimized page outline; returns `{ full, incremental? }`;\n options `{ track?, depth?, timeout? }`\n- `page.getByRole(role, { name })` / `page.getByText(text)` \u2014 semantic locators (survive re-renders)\n- `page.textContent(sel)` / `page.innerText(sel)` / `page.innerHTML(sel)` /\n `page.getAttribute(sel, name)` \u2014 read by selector\n- `page.inputValue(sel)` / `page.isChecked(sel)` / `page.isVisible(sel)` / `page.isHidden(sel)` \u2014\n input and visibility state\n- `page.fill(sel, value)` / `page.click(sel)` / `page.type(sel, text)` / `page.press(sel, key)` \u2014\n act on elements\n- `page.waitForSelector(sel, { state, timeout })` (`state`: `"attached"` / `"visible"` /\n `"hidden"` / `"detached"`) / `page.waitForURL(pattern)` / `page.waitForLoadState(state)` /\n `page.waitForFunction(fn)` / `page.waitForTimeout(ms)` \u2014 waiting\n- `page.screenshot({ fullPage })` \u2014 capture a screenshot Buffer; save it with `saveScreenshot(...)`\n- `page.evaluate(fn[, arg])` / `page.$eval(sel, fn)` / `page.$$eval(sel, fn)` \u2014 run plain\n JavaScript in the page context (real DOM; args/returns must be serializable)\n- `page.locator(sel)` \u2014 a Locator for chained actions (`.click()`, `.fill(value)`,\n `.textContent()`, `.first()`, \u2026)\n- `page.keyboard.press/type/down/up(...)` / `page.mouse.move/click/down/up(...)` \u2014 low-level input\n- `page.reload()` / `page.goBack()` / `page.goForward()` \u2014 history;\n `page.content()` / `page.setContent(html)` \u2014 full HTML\n- `page.on("console", handler)` \u2014 observe page console events';
9977
+ var API_PLAYWRIGHT_NOTE = "Pages returned by `browser.getPage()` and `browser.newPage()` are full Playwright Page objects \u2014\nthe same API (`goto`, `click`, `fill`, `locator`, `evaluate`, `getByRole`, `waitForSelector`, \u2026):\nhttps://playwright.dev/docs/api/class-page";
9978
+ var API_SANDBOX_ENV = "Scripts execute inside a QuickJS WASM sandbox with no arbitrary access to the host system.\nThis is NOT Node.js \u2014 there is no module system and no Node API:\n\n- `require()` / `import()` \u2014 no module loading; inline any helpers in the script\n- `process`, `fs` / `path` / `os` \u2014 no process or direct filesystem access (use the file helpers)\n- `fetch` / `WebSocket` \u2014 no direct network access (the page does the networking)\n- `__dirname` / `__filename` \u2014 no path globals\n\nMemory and CPU limits are enforced, and both CPU time and wall-clock time are bounded \u2014 infinite\nloops or never-settling promises abort the script. Values crossing `evaluate` / `$eval` must be\nJSON-serializable.";
9979
+ var API_SNAPSHOT = '- `page.snapshotForAI()` returns `{ full, incremental? }` \u2014 `full` is an aria outline of the\n page: roles, accessible names, `[ref=eN]` markers on actionable nodes. Read it to pick a\n semantic selector \u2014 `page.getByRole("button", { name: "Continue" })`,\n `page.getByText("Sign in")` \u2014 then act.\n- Options `{ track?, depth?, timeout? }`: re-run `page.snapshotForAI({ track: "main" })` after\n the page changes to get just the `incremental` diff; `{ depth: N }` caps the tree on huge\n pages; `timeout` bounds the walk.\n- `page.locator("aria-ref=e12")` works for an immediate action in the same script only \u2014 refs go\n stale across steps and after navigations. Prefer re-deriving a semantic selector.';
9980
+ var EX_INSPECT_TABS = "const tabs = await browser.listPages();\nconsole.log(JSON.stringify(tabs, null, 2));";
9981
+ var EX_SNAPSHOT = 'const page = await browser.getPage("main");\nconst snap = await page.snapshotForAI(); // { full, incremental? }\nconsole.log(page.url(), await page.title());\nconsole.log(snap.full); // aria outline \u2014 pick a role/text selector from this\n// then act: await page.getByRole("button", { name: "Continue" }).click();\n// after changes, page.snapshotForAI({ track: "main" }) returns just the incremental diff';
9982
+ var RULE_DATA_PASSING = '- Browser state persists across steps: named pages (and their cookies) stay open between scripts\n within a session \u2014 reuse the same page name so each step picks up where the last left off.\n- Anonymous `newPage()` tabs are closed when each script ends.\n- To pass values between steps: `writeFile("state.json", JSON.stringify(x))` in one step,\n `JSON.parse(await readFile("state.json"))` in the next.';
9983
+ var RULE_DEV_SERVER = 'For local dev servers (Next.js, Vite, \u2026) prefer\n`await page.goto(url, { waitUntil: "domcontentloaded" })` \u2014 the default `"load"` wait can hang\non HMR, streaming, or other long-lived dev-server connections. Use `"load"` only when you\nspecifically need every subresource to finish loading.';
9984
+ var RULE_FAIL_FAST = "- End each script by logging the state you need for the next decision \u2014 stdout is your\n observation channel.\n- Use short timeouts (`canary run --timeout 10`) so a step fails fast instead of hanging on a\n missing element.\n- In assertion / extraction steps, degrade gracefully \u2014 log a `WARN` / `FAIL` line instead of\n crashing, so the step still records its evidence. While exploring, a missed selector means\n look again (snapshot, fix, retry as a new step), not a silent fallback.\n- End before you stop: `canary stop` shuts the daemon down and aborts any live session,\n skipping its report.html \u2014 always `canary session end <id>` first.";
9985
+ var RULE_OBSERVE_FIRST = "- Unknown page? Snapshot first, then act: read `(await page.snapshotForAI()).full` to see what\n is there, pick a semantic selector from it (`getByRole`, `getByText`), then interact. Never\n guess selectors blind.\n- Known page or selectors? Skip the snapshot and use direct selectors \u2014 faster and more reliable.";
9986
+ var RULE_SCREENSHOT = "After each `canary run --step`, the daemon auto-captures ONE screenshot of the step's\nlast-opened tab and binds it to that step in the report. So:\n\n- Keep one primary named page per step \u2014 the report screenshot is always the page you mean.\n- If a step opens several tabs, open the one you want featured last.\n- `saveScreenshot(...)` images land in `~/.canary/tmp/` and are NOT in the report \u2014 they're\n extras for debugging.";
9987
+
9988
+ // ../../packages/cli-kit/src/scripting-help.ts
9989
+ var indent = (text, prefix) => text.split("\n").map((line) => line.length > 0 ? prefix + line : line).join("\n");
9990
+ var SANDBOX_ENVIRONMENT = `SANDBOX ENVIRONMENT:
9991
+ ${indent(API_SANDBOX_ENV, " ")}
9992
+
9993
+ ${indent(API_GLOBALS, " ")}`;
9994
+ var SCRIPT_API = `Script API available inside every script:
9995
+ ${indent(API_BROWSER, " ")}
9996
+
9997
+ ${indent(API_FILE_HELPERS, " ")}
9998
+
9999
+ ${indent(API_CONSOLE, " ")}`;
10000
+ var PLAYWRIGHT_PAGE_NOTE = API_PLAYWRIGHT_NOTE;
10001
+ function sandboxReference() {
10002
+ return `${SANDBOX_ENVIRONMENT}
10003
+
10004
+ ${SCRIPT_API}
10005
+
10006
+ ${PLAYWRIGHT_PAGE_NOTE}`;
10007
+ }
10008
+ var sessionExample = (scriptBody, opts) => [
10009
+ `canary run --session "$id" --step ${opts?.step ?? "<name>"} <<'EOF'`,
10010
+ scriptBody,
10011
+ "EOF"
10012
+ ].join("\n");
10013
+ var INSPECT_PAGE_BODY = `const page = await browser.getPage("TARGET_ID_HERE");
10014
+ console.log(JSON.stringify({
10015
+ url: page.url(),
10016
+ title: await page.title(),
10017
+ }, null, 2));`;
10018
+ var SCREENSHOT_BODY = `const page = await browser.getPage("main");
10019
+ const buf = await page.screenshot();
10020
+ const path = await saveScreenshot(buf, "debug.png");
10021
+ console.log(path);`;
10022
+ var WAITING_BODY = `const page = await browser.getPage("search-results");
10023
+ await page.waitForSelector(".results");
10024
+ await page.waitForURL("**/success");
10025
+ console.log(JSON.stringify({
10026
+ url: page.url(),
10027
+ title: await page.title(),
10028
+ }, null, 2));`;
10029
+ var ERROR_RECOVERY_BODY = `const page = await browser.getPage("checkout");
10030
+ const path = await saveScreenshot(await page.screenshot(), "debug.png");
10031
+ console.log(JSON.stringify({
10032
+ screenshot: path,
10033
+ url: page.url(),
10034
+ title: await page.title(),
10035
+ }, null, 2));`;
10036
+ var POWERSHELL_BLOCK = ` On Windows/PowerShell, use here-strings to pipe multiline scripts:
10037
+ @"
10038
+ const page = await browser.getPage("main");
10039
+ console.log(await page.title());
10040
+ "@ | canary-browser --connect`;
10041
+ var CONNECTING_SECTION = ` Connecting to a running Chrome instance:
10042
+ Auto-discover Chrome with debugging enabled:
10043
+ canary-browser --connect <<'EOF'
10044
+ const page = await browser.getPage("main");
10045
+ console.log(await page.title());
10046
+ EOF
10047
+
10048
+ Connect to a specific CDP endpoint:
10049
+ canary-browser --connect http://localhost:9222 <<'EOF'
10050
+ const page = await browser.getPage("main");
10051
+ console.log(await page.title());
10052
+ EOF
10053
+
10054
+ To launch Chrome with debugging enabled:
10055
+ chrome.exe --remote-debugging-port=9222
10056
+ google-chrome --remote-debugging-port=9222
10057
+
10058
+ Or visit chrome://inspect/#remote-debugging to configure.`;
10059
+ function buildScriptingGuide(options) {
10060
+ const { heading, example } = options;
10061
+ const browserExtras = options.browserExtras === true;
10062
+ const intro = [
10063
+ `${heading}`,
10064
+ " Write small, focused scripts. Each script should do ONE thing: navigate, click, fill, or check.",
10065
+ " End each script by logging the state you need for the next decision.",
10066
+ ' Use descriptive page names like "login", "checkout", or "results" instead of "page1".',
10067
+ ' Named pages from browser.getPage("name") persist between script runs, so you usually do not need to re-navigate.',
10068
+ " Inside page.evaluate(...), write plain JavaScript only - no TypeScript syntax in the browser context.",
10069
+ ...browserExtras ? [POWERSHELL_BLOCK] : []
10070
+ ].join("\n");
10071
+ const quickInspection = [
10072
+ " Quick inspection:",
10073
+ indent(
10074
+ example(EX_INSPECT_TABS, { step: "inspect", connect: true }),
10075
+ " "
10076
+ ),
10077
+ "",
10078
+ indent(
10079
+ example(INSPECT_PAGE_BODY, { step: "inspect", connect: true }),
10080
+ " "
10081
+ )
10082
+ ].join("\n");
10083
+ const aiSnapshots = [
10084
+ " AI snapshots for element discovery:",
10085
+ indent(example(EX_SNAPSHOT, { step: "discover" }), " "),
10086
+ "",
10087
+ indent(API_SNAPSHOT, " ")
10088
+ ].join("\n");
10089
+ const choosingApproach = [
10090
+ " Choosing your approach:",
10091
+ indent(RULE_OBSERVE_FIRST, " ")
10092
+ ].join("\n");
10093
+ const screenshots = [
10094
+ " Screenshots for visual state:",
10095
+ indent(example(SCREENSHOT_BODY, { step: "capture" }), " ")
10096
+ ].join("\n");
10097
+ const waiting = [
10098
+ " Waiting patterns:",
10099
+ indent(example(WAITING_BODY, { step: "wait" }), " ")
10100
+ ].join("\n");
10101
+ const devServer = [
10102
+ " Dev server navigation:",
10103
+ indent(RULE_DEV_SERVER, " ")
10104
+ ].join("\n");
10105
+ const errorRecovery = [
10106
+ " Error recovery:",
10107
+ " If a script fails, the page usually stays where it stopped.",
10108
+ " Reconnect to the same page name, take a screenshot, and log the URL/title:",
10109
+ indent(example(ERROR_RECOVERY_BODY, { step: "recover" }), " ")
10110
+ ].join("\n");
10111
+ const playwrightMethods = [
10112
+ " Common Playwright Page methods:",
10113
+ indent(API_PLAYWRIGHT_METHODS, " ")
10114
+ ].join("\n");
10115
+ const tips = [
10116
+ " Tips:",
10117
+ " - Use console.log(JSON.stringify(...)) for structured output.",
10118
+ " - Prefer page.snapshotForAI() for structure; use screenshots when visual layout or styling matters.",
10119
+ " - Keep page names stable across scripts so you can resume work after failures.",
10120
+ ...browserExtras ? [
10121
+ " - Each --browser name maps to a separate daemon-managed browser instance.",
10122
+ " - Use --connect to attach to an existing browser; omit the URL to auto-discover Chrome with debugging enabled."
10123
+ ] : [],
10124
+ " - Use short timeouts (--timeout 10) so scripts fail fast instead of hanging on missing elements.",
10125
+ ...browserExtras ? [
10126
+ " - Add --headless for unattended automation; omit it when you want to watch the browser window."
10127
+ ] : []
10128
+ ].join("\n");
10129
+ return [
10130
+ intro,
10131
+ quickInspection,
10132
+ aiSnapshots,
10133
+ choosingApproach,
10134
+ screenshots,
10135
+ waiting,
10136
+ devServer,
10137
+ errorRecovery,
10138
+ playwrightMethods,
10139
+ ...browserExtras ? [CONNECTING_SECTION] : [],
10140
+ tips
10141
+ ].join("\n\n");
10142
+ }
10143
+
10144
+ // ../../packages/cli-kit/src/index.ts
9970
10145
  function requestId(prefix) {
9971
10146
  return `${prefix}-${Date.now()}-${process.pid}`;
9972
10147
  }
@@ -15034,19 +15209,19 @@ WHAT IS CAPTURED (per session; toggle on \`session start\`):
15034
15209
  screenshots one per step, auto-captured from the step's last-opened page
15035
15210
 
15036
15211
  Artifacts live under ~/.canary/sessions/<id>/ (session.json, results.json, report.html, trace.zip, \u2026).
15037
- Scripts use the same sandboxed API as the engine \u2014 see \`canary-browser --help\` for the full reference.`;
15212
+ Scripts run in a QuickJS sandbox (not Node.js) with a pre-connected \`browser\` global \u2014 the full
15213
+ reference follows; the SCRIPTING GUIDE after the command list has worked examples.
15214
+
15215
+ ${sandboxReference()}`;
15038
15216
  var USAGE_GUIDE = `SESSION WORKFLOW GUIDE:
15039
15217
  Structure a session as a sequence of small steps \u2014 one script per step (open, act, assert).
15040
15218
  Each \`canary run --step <name>\` is one step in the report, with its own trace group and ONE
15041
- auto-captured screenshot (taken from the LAST page opened during that step). So:
15042
- - Use ONE primary named page per step.
15043
- - Reuse the same page name across steps to "click through" like a user \u2014 named pages persist
15044
- across steps within a session.
15045
- - If a step opens extra tabs, open the one you want featured in the report LAST.
15219
+ auto-captured screenshot.
15220
+
15221
+ ${indent(RULE_SCREENSHOT, " ")}
15046
15222
 
15047
15223
  Passing data between steps:
15048
- - Browser state (named pages, cookies) persists across steps automatically.
15049
- - For values: writeFile("state.json", JSON.stringify(x)) in one step, readFile it in the next.
15224
+ ${indent(RULE_DATA_PASSING, " ")}
15050
15225
 
15051
15226
  Reading results:
15052
15227
  canary session list List sessions (table; --json for machine output)
@@ -15054,12 +15229,18 @@ var USAGE_GUIDE = `SESSION WORKFLOW GUIDE:
15054
15229
  open ~/.canary/sessions/<id>/report.html The self-contained report
15055
15230
  canary ui Browse, search, and organize all sessions
15056
15231
 
15232
+ Step discipline:
15233
+ ${indent(RULE_FAIL_FAST, " ")}
15234
+
15057
15235
  Tips:
15058
15236
  - \`--json\` (global) emits machine-readable JSON on stdout; \`-v\`/\`--verbose\` raises stderr logging.
15059
- - \`canary run --timeout 30\` fails a step fast instead of hanging on a missing element.
15060
15237
  - \`canary session end --stop-daemon\` shuts the daemon down if nothing else is using it.
15061
- - \`canary stop\` shuts the whole background daemon down (and every browser it's running).
15062
- - Need a quick one-off with NO recording? Use \`canary-browser run\` instead of a session.`;
15238
+ - Need a quick one-off with NO recording? Use \`canary-browser run\` instead of a session.
15239
+
15240
+ ${buildScriptingGuide({
15241
+ example: sessionExample,
15242
+ heading: "SCRIPTING GUIDE:"
15243
+ })}`;
15063
15244
  var SESSION_START_LONG_ABOUT = `Start a capture-enabled session and print its id.
15064
15245
 
15065
15246
  Capture is on by default \u2014 disable per stream with --no-trace / --no-video / --no-har / --no-console.
@@ -15069,10 +15250,15 @@ Use --headless for unattended runs; omit it to watch the browser window.
15069
15250
  id=$(canary session start --name "smoke" --headless --no-video)`;
15070
15251
  var RUN_LONG_ABOUT = `Run a script as one step inside a session.
15071
15252
 
15072
- The script (a FILE, or stdin if omitted) executes in the sandbox the same way \`canary-browser run\`
15073
- does \u2014 top-level await, with \`browser\` and \`console\`. The step's name labels it in the report and
15074
- owns one auto-captured screenshot (from the last page opened during the step).
15253
+ The script (a FILE, or stdin if omitted) executes as top-level JavaScript with \`await\` in a
15254
+ sandboxed QuickJS runtime \u2014 full reference below. The step's name labels it in the report and
15255
+ owns ONE auto-captured screenshot (taken from the LAST page opened during the step). Named
15256
+ pages persist across steps within the session, so each step picks up where the last left off;
15257
+ pass values between steps with writeFile/readFile.
15258
+
15259
+ ${sandboxReference()}
15075
15260
 
15261
+ Examples:
15076
15262
  canary run open.js --session "$id" --step open
15077
15263
  echo 'const p = await browser.getPage("home"); await p.goto("https://example.com");' \\
15078
15264
  | canary run --session "$id" --step home --timeout 30`;
@@ -17036,26 +17222,25 @@ function findWorkspaceUiDir() {
17036
17222
  async function resolveUiServer() {
17037
17223
  const override = process.env.CANARY_UI_SERVER;
17038
17224
  if (override && await isFile(override)) {
17039
- return { kind: "standalone", serverJs: override };
17225
+ return { kind: "standalone", serverEntry: override };
17040
17226
  }
17041
17227
  const workspaceDir = findWorkspaceUiDir();
17042
17228
  if (!workspaceDir) {
17043
17229
  return null;
17044
17230
  }
17045
- const serverJs = import_node_path9.default.join(
17231
+ const serverEntry = import_node_path9.default.join(workspaceDir, "dist", "server", "entry.mjs");
17232
+ if (await isFile(serverEntry)) {
17233
+ return { kind: "standalone", serverEntry };
17234
+ }
17235
+ const astroBin = import_node_path9.default.join(
17046
17236
  workspaceDir,
17047
- ".next",
17048
- "standalone",
17049
- "apps",
17050
- "canary-ui",
17051
- "server.js"
17237
+ "node_modules",
17238
+ "astro",
17239
+ "bin",
17240
+ "astro.mjs"
17052
17241
  );
17053
- if (await isFile(serverJs)) {
17054
- return { kind: "standalone", serverJs };
17055
- }
17056
- const nextBin = import_node_path9.default.join(workspaceDir, "node_modules", ".bin", "next");
17057
- if (await isFile(nextBin)) {
17058
- return { kind: "dev", nextBin, workspaceDir };
17242
+ if (await isFile(astroBin)) {
17243
+ return { kind: "dev", astroBin, workspaceDir };
17059
17244
  }
17060
17245
  return null;
17061
17246
  }
@@ -17116,9 +17301,10 @@ async function uiCommand(args) {
17116
17301
  const baseEnv = { ...process.env, CANARY_UI_ROOT: root };
17117
17302
  let child;
17118
17303
  if (resolved.kind === "standalone") {
17119
- child = (0, import_node_child_process4.spawn)(process.execPath, [resolved.serverJs], {
17120
- cwd: import_node_path10.default.dirname(resolved.serverJs),
17121
- env: { ...baseEnv, HOSTNAME: host, PORT: String(port) },
17304
+ child = (0, import_node_child_process4.spawn)(process.execPath, [resolved.serverEntry], {
17305
+ cwd: import_node_path10.default.dirname(resolved.serverEntry),
17306
+ // Astro's node adapter reads HOST (the old Next server read HOSTNAME).
17307
+ env: { ...baseEnv, HOST: host, PORT: String(port) },
17122
17308
  stdio: ["ignore", "inherit", "inherit"]
17123
17309
  });
17124
17310
  } else {
@@ -17127,7 +17313,7 @@ async function uiCommand(args) {
17127
17313
  );
17128
17314
  child = (0, import_node_child_process4.spawn)(
17129
17315
  process.execPath,
17130
- [resolved.nextBin, "dev", "-p", String(port), "-H", host],
17316
+ [resolved.astroBin, "dev", "--port", String(port), "--host", host],
17131
17317
  {
17132
17318
  cwd: resolved.workspaceDir,
17133
17319
  env: baseEnv,
@@ -17175,7 +17361,7 @@ async function uiCommand(args) {
17175
17361
 
17176
17362
  // src/cli.ts
17177
17363
  var { Command: Command2, InvalidArgumentError: InvalidArgumentError2 } = esm_exports;
17178
- var VERSION = "0.2.1";
17364
+ var VERSION = "0.4.0";
17179
17365
  var ExitCodeError = class extends Error {
17180
17366
  code;
17181
17367
  constructor(code) {