slapify 0.0.16 → 0.0.17

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.
Files changed (49) hide show
  1. package/dist/ai/interpreter.js +1 -331
  2. package/dist/browser/agent.js +1 -485
  3. package/dist/cli.js +1 -1553
  4. package/dist/config/loader.js +1 -305
  5. package/dist/index.js +1 -262
  6. package/dist/parser/flow.js +1 -117
  7. package/dist/perf/audit.js +1 -635
  8. package/dist/report/generator.js +1 -641
  9. package/dist/runner/index.js +1 -744
  10. package/dist/task/index.js +1 -4
  11. package/dist/task/report.js +1 -740
  12. package/dist/task/runner.js +1 -1362
  13. package/dist/task/session.js +1 -153
  14. package/dist/task/tools.js +1 -258
  15. package/dist/task/types.js +1 -2
  16. package/dist/types.js +1 -2
  17. package/package.json +6 -3
  18. package/dist/ai/interpreter.d.ts.map +0 -1
  19. package/dist/ai/interpreter.js.map +0 -1
  20. package/dist/browser/agent.d.ts.map +0 -1
  21. package/dist/browser/agent.js.map +0 -1
  22. package/dist/cli.d.ts.map +0 -1
  23. package/dist/cli.js.map +0 -1
  24. package/dist/config/loader.d.ts.map +0 -1
  25. package/dist/config/loader.js.map +0 -1
  26. package/dist/index.d.ts.map +0 -1
  27. package/dist/index.js.map +0 -1
  28. package/dist/parser/flow.d.ts.map +0 -1
  29. package/dist/parser/flow.js.map +0 -1
  30. package/dist/perf/audit.d.ts.map +0 -1
  31. package/dist/perf/audit.js.map +0 -1
  32. package/dist/report/generator.d.ts.map +0 -1
  33. package/dist/report/generator.js.map +0 -1
  34. package/dist/runner/index.d.ts.map +0 -1
  35. package/dist/runner/index.js.map +0 -1
  36. package/dist/task/index.d.ts.map +0 -1
  37. package/dist/task/index.js.map +0 -1
  38. package/dist/task/report.d.ts.map +0 -1
  39. package/dist/task/report.js.map +0 -1
  40. package/dist/task/runner.d.ts.map +0 -1
  41. package/dist/task/runner.js.map +0 -1
  42. package/dist/task/session.d.ts.map +0 -1
  43. package/dist/task/session.js.map +0 -1
  44. package/dist/task/tools.d.ts.map +0 -1
  45. package/dist/task/tools.js.map +0 -1
  46. package/dist/task/types.d.ts.map +0 -1
  47. package/dist/task/types.js.map +0 -1
  48. package/dist/types.d.ts.map +0 -1
  49. package/dist/types.js.map +0 -1
@@ -1,485 +1 @@
1
- import { execSync } from "child_process";
2
- // Transient errors that should trigger automatic retry
3
- const TRANSIENT_ERRORS = [
4
- "Execution context was destroyed",
5
- "Target closed",
6
- "Navigation interrupted",
7
- "Protocol error",
8
- "Session closed",
9
- "Page crashed",
10
- "Frame was detached",
11
- "Cannot find context",
12
- ];
13
- /**
14
- * Wrapper around agent-browser CLI
15
- */
16
- export class BrowserAgent {
17
- config;
18
- isOpen = false;
19
- constructor(config = {}) {
20
- this.config = {
21
- headless: true,
22
- timeout: 30000,
23
- viewport: { width: 1280, height: 720 },
24
- ...config,
25
- };
26
- }
27
- /**
28
- * Check if an error is transient and should be retried
29
- */
30
- isTransientError(errorMessage) {
31
- return TRANSIENT_ERRORS.some((e) => errorMessage.toLowerCase().includes(e.toLowerCase()));
32
- }
33
- /**
34
- * Sleep for a given number of milliseconds
35
- */
36
- sleep(ms) {
37
- execSync(`sleep ${ms / 1000}`);
38
- }
39
- /**
40
- * Execute an agent-browser command with auto-retry for transient errors
41
- */
42
- exec(command, args = [], retries = 2) {
43
- const fullCommand = ["agent-browser", command, ...args].join(" ");
44
- // Set up environment with executable path if configured
45
- const env = { ...process.env };
46
- if (this.config.executablePath) {
47
- env.AGENT_BROWSER_EXECUTABLE_PATH = this.config.executablePath;
48
- }
49
- let lastError = null;
50
- for (let attempt = 0; attempt <= retries; attempt++) {
51
- try {
52
- const result = execSync(fullCommand, {
53
- encoding: "utf-8",
54
- timeout: this.config.timeout,
55
- stdio: ["pipe", "pipe", "pipe"],
56
- env,
57
- });
58
- return result.trim();
59
- }
60
- catch (error) {
61
- let errorMsg = error.message || error.stderr?.toString() || "";
62
- // Filter out non-fatal daemon warnings
63
- const isJustWarning = errorMsg.includes("daemon already running") ||
64
- errorMsg.includes("--executable-path ignored");
65
- // If we have stdout, return it (some commands output to stdout even on "error")
66
- if (error.stdout) {
67
- const stdout = error.stdout.toString().trim();
68
- // If it's just a warning and we have stdout, return stdout
69
- if (isJustWarning && stdout) {
70
- return stdout;
71
- }
72
- // Check if stdout contains an actual error message
73
- if (!this.isTransientError(stdout)) {
74
- return stdout;
75
- }
76
- }
77
- // If it's just a warning with no output, consider it success
78
- if (isJustWarning) {
79
- return "";
80
- }
81
- // Check if this is a transient error worth retrying
82
- if (this.isTransientError(errorMsg) && attempt < retries) {
83
- // Wait before retry (with exponential backoff)
84
- this.sleep(500 * (attempt + 1));
85
- continue;
86
- }
87
- lastError = new Error(`Browser command failed: ${fullCommand}\n${errorMsg}`);
88
- }
89
- }
90
- throw lastError || new Error(`Browser command failed: ${fullCommand}`);
91
- }
92
- /**
93
- * Check if agent-browser is installed
94
- */
95
- static isInstalled() {
96
- try {
97
- execSync("agent-browser --version", { stdio: "pipe" });
98
- return true;
99
- }
100
- catch {
101
- return false;
102
- }
103
- }
104
- /**
105
- * Install agent-browser and its browser
106
- */
107
- static install() {
108
- console.log("Installing agent-browser...");
109
- execSync("npm install -g agent-browser", { stdio: "inherit" });
110
- console.log("Installing browser...");
111
- execSync("agent-browser install", { stdio: "inherit" });
112
- }
113
- hasInitialized = false;
114
- /**
115
- * Navigate to a URL
116
- */
117
- async navigate(url) {
118
- const args = [];
119
- // On first navigation, close any existing browser to apply our settings
120
- // This ensures headed mode and executable path are applied correctly
121
- if (!this.hasInitialized &&
122
- (this.config.headless === false || this.config.executablePath)) {
123
- try {
124
- this.exec("close", []);
125
- // Give daemon time to fully shut down before reopening
126
- this.sleep(1000);
127
- }
128
- catch {
129
- // Ignore if no browser to close
130
- }
131
- this.hasInitialized = true;
132
- }
133
- if (this.config.headless === false) {
134
- args.push("--headed");
135
- }
136
- // Use custom executable path if specified
137
- if (this.config.executablePath) {
138
- args.push("--executable-path", `"${this.config.executablePath}"`);
139
- }
140
- const doOpen = () => this.exec("open", [url, ...args]);
141
- doOpen();
142
- this.isOpen = true;
143
- // If we're stuck on about:blank (e.g. daemon in bad state), retry open
144
- const maxNavigateRetries = 2;
145
- for (let r = 0; r < maxNavigateRetries; r++) {
146
- this.sleep(1500);
147
- try {
148
- const currentUrl = this.exec("get", ["url"]).trim();
149
- if (currentUrl &&
150
- currentUrl !== "about:blank" &&
151
- currentUrl !== "about:blank/") {
152
- break;
153
- }
154
- // Still on about:blank - close and try open again
155
- try {
156
- this.exec("close", []);
157
- this.sleep(1000);
158
- }
159
- catch {
160
- // ignore
161
- }
162
- doOpen();
163
- this.sleep(1500);
164
- }
165
- catch {
166
- break;
167
- }
168
- }
169
- // Set viewport if specified
170
- if (this.config.viewport) {
171
- this.exec("set", [
172
- "viewport",
173
- String(this.config.viewport.width),
174
- String(this.config.viewport.height),
175
- ]);
176
- }
177
- }
178
- /**
179
- * Get page snapshot (accessibility tree)
180
- */
181
- async snapshot(interactive = true) {
182
- const args = interactive ? ["-i"] : [];
183
- return this.exec("snapshot", args);
184
- }
185
- /**
186
- * Get page snapshot as JSON with refs
187
- */
188
- async snapshotJson() {
189
- const result = this.exec("snapshot", ["-i", "--json"]);
190
- try {
191
- const parsed = JSON.parse(result);
192
- return {
193
- snapshot: parsed.data?.snapshot || result,
194
- refs: parsed.data?.refs || {},
195
- };
196
- }
197
- catch {
198
- return { snapshot: result, refs: {} };
199
- }
200
- }
201
- /**
202
- * Click an element by ref or selector
203
- */
204
- async click(selector) {
205
- this.exec("click", [selector]);
206
- }
207
- /**
208
- * Fill an input field
209
- */
210
- async fill(selector, value) {
211
- this.exec("fill", [selector, `"${value}"`]);
212
- }
213
- /**
214
- * Type text (appends to existing value)
215
- */
216
- async type(selector, value) {
217
- this.exec("type", [selector, `"${value}"`]);
218
- }
219
- /**
220
- * Press a key
221
- */
222
- async press(key) {
223
- this.exec("press", [key]);
224
- }
225
- /**
226
- * Hover over an element
227
- */
228
- async hover(selector) {
229
- this.exec("hover", [selector]);
230
- }
231
- /**
232
- * Select dropdown option
233
- */
234
- async select(selector, value) {
235
- this.exec("select", [selector, value]);
236
- }
237
- /**
238
- * Scroll the page
239
- */
240
- async scroll(direction, amount) {
241
- const args = amount ? [direction, String(amount)] : [direction];
242
- this.exec("scroll", args);
243
- }
244
- /**
245
- * Wait for various conditions
246
- */
247
- async wait(condition) {
248
- if (typeof condition === "number") {
249
- this.exec("wait", [String(condition)]);
250
- }
251
- else if (condition.startsWith("text=")) {
252
- this.exec("wait", ["--text", `"${condition.substring(5)}"`]);
253
- }
254
- else if (condition.startsWith("url=")) {
255
- this.exec("wait", ["--url", `"${condition.substring(4)}"`]);
256
- }
257
- else {
258
- this.exec("wait", [condition]);
259
- }
260
- }
261
- /**
262
- * Take a screenshot
263
- */
264
- async screenshot(path, fullPage = false) {
265
- const args = [];
266
- if (path)
267
- args.push(path);
268
- if (fullPage)
269
- args.push("--full");
270
- return this.exec("screenshot", args);
271
- }
272
- /**
273
- * Get text content of an element
274
- */
275
- async getText(selector) {
276
- return this.exec("get", ["text", selector]);
277
- }
278
- /**
279
- * Get current URL
280
- */
281
- async getUrl() {
282
- return this.exec("get", ["url"]);
283
- }
284
- /**
285
- * Get page title
286
- */
287
- async getTitle() {
288
- return this.exec("get", ["title"]);
289
- }
290
- /**
291
- * Check if element is visible
292
- */
293
- async isVisible(selector) {
294
- const result = this.exec("is", ["visible", selector]);
295
- return result.toLowerCase().includes("true");
296
- }
297
- /**
298
- * Execute JavaScript
299
- */
300
- async evaluate(script) {
301
- return this.exec("eval", [`"${script.replace(/"/g, '\\"')}"`]);
302
- }
303
- /**
304
- * Set cookies
305
- */
306
- async setCookie(name, value) {
307
- // Escape special shell characters in cookie value
308
- const escapedValue = `'${value.replace(/'/g, "'\\''")}'`;
309
- this.exec("cookies", ["set", name, escapedValue]);
310
- }
311
- /**
312
- * Get all cookies
313
- */
314
- async getCookies() {
315
- const result = this.exec("cookies", ["get"]);
316
- // Parse cookie string: "name1=value1; name2=value2" or "name1=value1\nname2=value2"
317
- const cookies = [];
318
- if (!result || result.trim() === "")
319
- return cookies;
320
- // Split by newline or semicolon
321
- const parts = result.includes("\n")
322
- ? result.split("\n")
323
- : result.split("; ");
324
- for (const part of parts) {
325
- const trimmed = part.trim();
326
- if (!trimmed)
327
- continue;
328
- const eqIndex = trimmed.indexOf("=");
329
- if (eqIndex > 0) {
330
- cookies.push({
331
- name: trimmed.substring(0, eqIndex),
332
- value: trimmed.substring(eqIndex + 1),
333
- });
334
- }
335
- }
336
- return cookies;
337
- }
338
- /**
339
- * Get all localStorage
340
- */
341
- async getLocalStorage() {
342
- // Use single quotes to avoid shell escaping issues with parentheses
343
- const script = `'(function(){var r={};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);r[k]=localStorage.getItem(k);}return JSON.stringify(r);})()'`;
344
- const result = this.exec("eval", [script]);
345
- try {
346
- // Result might be double-quoted string, so parse twice if needed
347
- let parsed = JSON.parse(result);
348
- if (typeof parsed === "string") {
349
- parsed = JSON.parse(parsed);
350
- }
351
- return parsed || {};
352
- }
353
- catch {
354
- return {};
355
- }
356
- }
357
- /**
358
- * Get all sessionStorage
359
- */
360
- async getSessionStorage() {
361
- const script = `'(function(){var r={};for(var i=0;i<sessionStorage.length;i++){var k=sessionStorage.key(i);r[k]=sessionStorage.getItem(k);}return JSON.stringify(r);})()'`;
362
- const result = this.exec("eval", [script]);
363
- try {
364
- // Result might be double-quoted string, so parse twice if needed
365
- let parsed = JSON.parse(result);
366
- if (typeof parsed === "string") {
367
- parsed = JSON.parse(parsed);
368
- }
369
- return parsed || {};
370
- }
371
- catch {
372
- return {};
373
- }
374
- }
375
- /**
376
- * Set localStorage value
377
- */
378
- async setLocalStorage(key, value) {
379
- // Escape special shell characters
380
- const escapedValue = `'${value.replace(/'/g, "'\\''")}'`;
381
- this.exec("storage", ["local", "set", key, escapedValue]);
382
- }
383
- /**
384
- * Set sessionStorage value
385
- */
386
- async setSessionStorage(key, value) {
387
- // Escape special shell characters
388
- const escapedValue = `'${value.replace(/'/g, "'\\''")}'`;
389
- this.exec("storage", ["session", "set", key, escapedValue]);
390
- }
391
- /**
392
- * Go back
393
- */
394
- async goBack() {
395
- this.exec("back", []);
396
- }
397
- /**
398
- * Go forward
399
- */
400
- async goForward() {
401
- this.exec("forward", []);
402
- }
403
- /**
404
- * Reload page
405
- */
406
- async reload() {
407
- this.exec("reload", []);
408
- }
409
- /**
410
- * Close the browser
411
- */
412
- async close() {
413
- if (this.isOpen) {
414
- try {
415
- this.exec("close", []);
416
- }
417
- catch {
418
- // Ignore close errors
419
- }
420
- this.isOpen = false;
421
- }
422
- }
423
- /**
424
- * Get current browser state (with graceful error handling)
425
- */
426
- async getState() {
427
- // Get each piece of state individually, with fallbacks
428
- let url = "";
429
- let title = "";
430
- let snapshotData = { snapshot: "", refs: {} };
431
- try {
432
- url = await this.getUrl();
433
- }
434
- catch {
435
- url = "unknown";
436
- }
437
- try {
438
- title = await this.getTitle();
439
- }
440
- catch {
441
- title = "";
442
- }
443
- try {
444
- snapshotData = await this.snapshotJson();
445
- }
446
- catch {
447
- // Try plain snapshot as fallback
448
- try {
449
- const plainSnapshot = await this.snapshot();
450
- snapshotData = { snapshot: plainSnapshot, refs: {} };
451
- }
452
- catch {
453
- snapshotData = { snapshot: "Unable to get page snapshot", refs: {} };
454
- }
455
- }
456
- return {
457
- url,
458
- title,
459
- snapshot: snapshotData.snapshot,
460
- refs: snapshotData.refs,
461
- };
462
- }
463
- /**
464
- * Wait for page to be stable (no navigation in progress)
465
- */
466
- async waitForStable(timeout = 2000) {
467
- const startTime = Date.now();
468
- let lastUrl = "";
469
- while (Date.now() - startTime < timeout) {
470
- try {
471
- const currentUrl = await this.getUrl();
472
- if (currentUrl === lastUrl && currentUrl !== "about:blank") {
473
- // URL hasn't changed, page is likely stable
474
- return;
475
- }
476
- lastUrl = currentUrl;
477
- }
478
- catch {
479
- // Ignore errors during stability check
480
- }
481
- this.sleep(200);
482
- }
483
- }
484
- }
485
- //# sourceMappingURL=agent.js.map
1
+ import{execSync as e}from"child_process";const t=["Execution context was destroyed","Target closed","Navigation interrupted","Protocol error","Session closed","Page crashed","Frame was detached","Cannot find context"];export class BrowserAgent{config;isOpen=!1;constructor(e={}){this.config={headless:!0,timeout:3e4,viewport:{width:1280,height:720},...e}}isTransientError(e){return t.some(t=>e.toLowerCase().includes(t.toLowerCase()))}sleep(t){e("sleep "+t/1e3)}exec(t,s=[],i=2){const r=["agent-browser",t,...s].join(" "),n={...process.env};this.config.executablePath&&(n.AGENT_BROWSER_EXECUTABLE_PATH=this.config.executablePath);let a=null;for(let t=0;t<=i;t++)try{return e(r,{encoding:"utf-8",timeout:this.config.timeout,stdio:["pipe","pipe","pipe"],env:n}).trim()}catch(e){let s=e.message||e.stderr?.toString()||"";const n=s.includes("daemon already running")||s.includes("--executable-path ignored");if(e.stdout){const t=e.stdout.toString().trim();if(n&&t)return t;if(!this.isTransientError(t))return t}if(n)return"";if(this.isTransientError(s)&&t<i){this.sleep(500*(t+1));continue}a=new Error(`Browser command failed: ${r}\n${s}`)}throw a||new Error(`Browser command failed: ${r}`)}static isInstalled(){try{return e("agent-browser --version",{stdio:"pipe"}),!0}catch{return!1}}static install(){console.log("Installing agent-browser..."),e("npm install -g agent-browser",{stdio:"inherit"}),console.log("Installing browser..."),e("agent-browser install",{stdio:"inherit"})}hasInitialized=!1;async navigate(e){const t=[];if(!this.hasInitialized&&(!1===this.config.headless||this.config.executablePath)){try{this.exec("close",[]),this.sleep(1e3)}catch{}this.hasInitialized=!0}!1===this.config.headless&&t.push("--headed"),this.config.executablePath&&t.push("--executable-path",`"${this.config.executablePath}"`);const s=()=>this.exec("open",[e,...t]);s(),this.isOpen=!0;for(let e=0;e<2;e++){this.sleep(1500);try{const e=this.exec("get",["url"]).trim();if(e&&"about:blank"!==e&&"about:blank/"!==e)break;try{this.exec("close",[]),this.sleep(1e3)}catch{}s(),this.sleep(1500)}catch{break}}this.config.viewport&&this.exec("set",["viewport",String(this.config.viewport.width),String(this.config.viewport.height)])}async snapshot(e=!0){const t=e?["-i"]:[];return this.exec("snapshot",t)}async snapshotJson(){const e=this.exec("snapshot",["-i","--json"]);try{const t=JSON.parse(e);return{snapshot:t.data?.snapshot||e,refs:t.data?.refs||{}}}catch{return{snapshot:e,refs:{}}}}async click(e){this.exec("click",[e])}async fill(e,t){this.exec("fill",[e,`"${t}"`])}async type(e,t){this.exec("type",[e,`"${t}"`])}async press(e){this.exec("press",[e])}async hover(e){this.exec("hover",[e])}async select(e,t){this.exec("select",[e,t])}async scroll(e,t){const s=t?[e,String(t)]:[e];this.exec("scroll",s)}async wait(e){"number"==typeof e?this.exec("wait",[String(e)]):e.startsWith("text=")?this.exec("wait",["--text",`"${e.substring(5)}"`]):e.startsWith("url=")?this.exec("wait",["--url",`"${e.substring(4)}"`]):this.exec("wait",[e])}async screenshot(e,t=!1){const s=[];return e&&s.push(e),t&&s.push("--full"),this.exec("screenshot",s)}async getText(e){return this.exec("get",["text",e])}async getUrl(){return this.exec("get",["url"])}async getTitle(){return this.exec("get",["title"])}async isVisible(e){return this.exec("is",["visible",e]).toLowerCase().includes("true")}async evaluate(e){return this.exec("eval",[`"${e.replace(/"/g,'\\"')}"`])}async setCookie(e,t){const s=`'${t.replace(/'/g,"'\\''")}'`;this.exec("cookies",["set",e,s])}async getCookies(){const e=this.exec("cookies",["get"]),t=[];if(!e||""===e.trim())return t;const s=e.includes("\n")?e.split("\n"):e.split("; ");for(const e of s){const s=e.trim();if(!s)continue;const i=s.indexOf("=");i>0&&t.push({name:s.substring(0,i),value:s.substring(i+1)})}return t}async getLocalStorage(){const e=this.exec("eval",["'(function(){var r={};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);r[k]=localStorage.getItem(k);}return JSON.stringify(r);})()'"]);try{let t=JSON.parse(e);return"string"==typeof t&&(t=JSON.parse(t)),t||{}}catch{return{}}}async getSessionStorage(){const e=this.exec("eval",["'(function(){var r={};for(var i=0;i<sessionStorage.length;i++){var k=sessionStorage.key(i);r[k]=sessionStorage.getItem(k);}return JSON.stringify(r);})()'"]);try{let t=JSON.parse(e);return"string"==typeof t&&(t=JSON.parse(t)),t||{}}catch{return{}}}async setLocalStorage(e,t){const s=`'${t.replace(/'/g,"'\\''")}'`;this.exec("storage",["local","set",e,s])}async setSessionStorage(e,t){const s=`'${t.replace(/'/g,"'\\''")}'`;this.exec("storage",["session","set",e,s])}async goBack(){this.exec("back",[])}async goForward(){this.exec("forward",[])}async reload(){this.exec("reload",[])}async close(){if(this.isOpen){try{this.exec("close",[])}catch{}this.isOpen=!1}}async getState(){let e="",t="",s={snapshot:"",refs:{}};try{e=await this.getUrl()}catch{e="unknown"}try{t=await this.getTitle()}catch{t=""}try{s=await this.snapshotJson()}catch{try{s={snapshot:await this.snapshot(),refs:{}}}catch{s={snapshot:"Unable to get page snapshot",refs:{}}}}return{url:e,title:t,snapshot:s.snapshot,refs:s.refs}}async waitForStable(e=2e3){const t=Date.now();let s="";for(;Date.now()-t<e;){try{const e=await this.getUrl();if(e===s&&"about:blank"!==e)return;s=e}catch{}this.sleep(200)}}}