@usecanary/cli 0.2.0 → 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 +2 -1
- package/dist/cli.cjs +223 -37
- package/dist/cli.js +223 -37
- package/dist/cli.js.map +4 -4
- package/package.json +7 -7
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
|
|
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,
|
|
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 -
|
|
7469
|
+
const columnWidth = width - indent2;
|
|
7470
7470
|
if (columnWidth < minColumnWidth) return str;
|
|
7471
|
-
const leadingStr = str.slice(0,
|
|
7472
|
-
const columnText = str.slice(
|
|
7473
|
-
const indentString = " ".repeat(
|
|
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
|
|
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
|
|
15042
|
-
|
|
15043
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
15062
|
-
|
|
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
|
|
15073
|
-
|
|
15074
|
-
owns
|
|
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",
|
|
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
|
|
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
|
-
"
|
|
17048
|
-
"
|
|
17049
|
-
"
|
|
17050
|
-
"
|
|
17051
|
-
"server.js"
|
|
17237
|
+
"node_modules",
|
|
17238
|
+
"astro",
|
|
17239
|
+
"bin",
|
|
17240
|
+
"astro.mjs"
|
|
17052
17241
|
);
|
|
17053
|
-
if (await isFile(
|
|
17054
|
-
return { kind: "
|
|
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.
|
|
17120
|
-
cwd: import_node_path10.default.dirname(resolved.
|
|
17121
|
-
|
|
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.
|
|
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.
|
|
17364
|
+
var VERSION = "0.4.0";
|
|
17179
17365
|
var ExitCodeError = class extends Error {
|
|
17180
17366
|
code;
|
|
17181
17367
|
constructor(code) {
|