sliccy 1.0.2 → 1.0.4

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,8 +108,6 @@ For the full Electron workflow, see [docs/electron.md](docs/electron.md).
108
108
 
109
109
  ## Screenshots and proof
110
110
 
111
-
112
-
113
111
  ## How it works
114
112
 
115
113
  SLICC shares one core across the CLI, extension, and Electron modes. The browser is not just where you view the product — it is where the agent runtime lives.
@@ -136,6 +134,7 @@ Why the name? SLICC stands for **Self-Licking Ice Cream Cone**: a recursive syst
136
134
  ## API Keys and Providers
137
135
 
138
136
  To use SLICC, you need an LLM provider. SLICC is very much a BYOT (bring your own tokens) affair. We have built-in support for many providers, and these have actually been tested.
137
+
139
138
  - Adobe (for AEM customers. Talk to the team to get enabled)
140
139
  - AWS Bedrock (because enterprise)
141
140
  - AWS Bedrock CAMP (this is Adobe-internal. Did I say "because enterprise" already?)
@@ -76,7 +76,9 @@ export function resolveChromeLaunchProfile(options) {
76
76
  id: profile,
77
77
  displayName: definition.displayName,
78
78
  userDataDir: join(resolveQaProfilesRoot(options.projectRoot), profile),
79
- extensionPath: definition.loadsExtension ? join(options.projectRoot, 'dist', 'extension') : null,
79
+ extensionPath: definition.loadsExtension
80
+ ? join(options.projectRoot, 'dist', 'extension')
81
+ : null,
80
82
  };
81
83
  }
82
84
  export function buildChromeLaunchArgs(options) {
@@ -142,7 +144,10 @@ function resolveMacAppBundle(appPath, platform, existsSyncImpl) {
142
144
  return null;
143
145
  // Derive the binary name from the bundle name:
144
146
  // "Google Chrome.app" → "Google Chrome"
145
- const bundleName = appPath.split('/').pop().replace(/\.app$/, '');
147
+ const bundleName = appPath
148
+ .split('/')
149
+ .pop()
150
+ .replace(/\.app$/, '');
146
151
  const candidate = join(appPath, 'Contents', 'MacOS', bundleName);
147
152
  return existsSyncImpl(candidate) ? candidate : null;
148
153
  }
@@ -197,8 +202,8 @@ export function findChromeExecutable(options = {}) {
197
202
  readdirSyncImpl,
198
203
  });
199
204
  return executablePreference === 'installed'
200
- ? installedChrome ?? chromeForTesting
201
- : chromeForTesting ?? installedChrome;
205
+ ? (installedChrome ?? chromeForTesting)
206
+ : (chromeForTesting ?? installedChrome);
202
207
  }
203
208
  async function readJsonFile(filePath) {
204
209
  try {
@@ -8,7 +8,7 @@
8
8
  const BUFFER_SIZE = 10;
9
9
  const WINDOW_MS = 60_000; // 1 minute
10
10
  function makeFingerprint(message) {
11
- return message
11
+ return (message
12
12
  // UUIDs
13
13
  .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<id>')
14
14
  // Hex strings (8+ chars, e.g. session IDs, target IDs)
@@ -17,7 +17,7 @@ function makeFingerprint(message) {
17
17
  .replace(/\{[^}]{20,}\}/g, '{…}')
18
18
  .replace(/\[[^\]]{20,}\]/g, '[…]')
19
19
  // Numbers (integers and floats)
20
- .replace(/\b\d+(\.\d+)?\b/g, '<n>');
20
+ .replace(/\b\d+(\.\d+)?\b/g, '<n>'));
21
21
  }
22
22
  export class CliLogDedup {
23
23
  entries = [];
@@ -35,7 +35,7 @@ export class CliLogDedup {
35
35
  // Evict stale entries
36
36
  this.evict(now);
37
37
  // Check for existing match
38
- const existing = this.entries.find(e => e.fingerprint === fp);
38
+ const existing = this.entries.find((e) => e.fingerprint === fp);
39
39
  if (existing) {
40
40
  existing.count++;
41
41
  return false;
@@ -11,9 +11,9 @@ function commandLineExecutableMatchesPattern(commandLine, pattern) {
11
11
  // Only match when the target app path is the executable itself, not an argument —
12
12
  // this avoids false positives when the path appears as a CLI flag (e.g. --kill /App.app).
13
13
  const executable = commandLine.trimStart().split(/\s+/)[0] ?? '';
14
- return executable === pattern
15
- || executable.startsWith(pattern + '/')
16
- || executable.startsWith(pattern + '\\');
14
+ return (executable === pattern ||
15
+ executable.startsWith(pattern + '/') ||
16
+ executable.startsWith(pattern + '\\'));
17
17
  }
18
18
  export function findMatchingElectronAppPids(runningProcesses, processMatchPatterns, currentPid = process.pid) {
19
19
  const matches = runningProcesses.filter((processInfo) => {
@@ -26,8 +26,8 @@ export function findMatchingElectronAppPids(runningProcesses, processMatchPatter
26
26
  if (/^(\/\S*\/)?(node|npx|tsx|npm|open|bash|zsh|sh|csh|fish|dash|timeout|env|sudo|caffeinate)\b/i.test(cmdTrimmed))
27
27
  return false;
28
28
  return processMatchPatterns.some((pattern) => {
29
- return commandLineExecutableMatchesPattern(processInfo.commandLine, pattern)
30
- || (processInfo.executablePath?.includes(pattern) ?? false);
29
+ return (commandLineExecutableMatchesPattern(processInfo.commandLine, pattern) ||
30
+ (processInfo.executablePath?.includes(pattern) ?? false));
31
31
  });
32
32
  });
33
33
  return Array.from(new Set(matches.map((processInfo) => processInfo.pid).filter((pid) => pid !== currentPid)));
@@ -175,7 +175,7 @@ app.on('will-quit', () => {
175
175
  void stopCliServer();
176
176
  });
177
177
  main().catch((error) => {
178
- const message = error instanceof Error ? error.stack ?? error.message : String(error);
178
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
179
179
  console.error('[electron-float] Fatal error:', message);
180
180
  void stopCliServer().finally(() => {
181
181
  app.exit(1);
@@ -21,10 +21,7 @@ export function resolveElectronAppExecutablePath(appPath, platform = process.pla
21
21
  return resolvedAppPath;
22
22
  }
23
23
  export function buildElectronAppProcessMatchPatterns(appPath, platform = process.platform) {
24
- return Array.from(new Set([
25
- resolve(appPath),
26
- resolveElectronAppExecutablePath(appPath, platform),
27
- ]));
24
+ return Array.from(new Set([resolve(appPath), resolveElectronAppExecutablePath(appPath, platform)]));
28
25
  }
29
26
  export function buildElectronAppLaunchSpec(appPath, options) {
30
27
  const platform = options.platform ?? process.platform;
@@ -83,7 +80,11 @@ export function buildElectronServerSpawnConfig(projectRoot, options) {
83
80
  }
84
81
  return {
85
82
  command: options.nodePath ?? process.env['npm_node_execpath'] ?? 'node',
86
- args: [resolve(projectRoot, 'dist/cli/index.js'), '--serve-only', `--cdp-port=${options.cdpPort}`],
83
+ args: [
84
+ resolve(projectRoot, 'dist/cli/index.js'),
85
+ '--serve-only',
86
+ `--cdp-port=${options.cdpPort}`,
87
+ ],
87
88
  };
88
89
  }
89
90
  export function getElectronServeOrigin(servePort) {
@@ -1,4 +1,4 @@
1
- import { mkdirSync, openSync, writeSync, closeSync, readdirSync, statSync, unlinkSync } from 'node:fs';
1
+ import { mkdirSync, openSync, writeSync, closeSync, readdirSync, statSync, unlinkSync, } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  // ---------------------------------------------------------------------------
@@ -45,7 +45,10 @@ function safeStringify(value) {
45
45
  }
46
46
  /** Generates a filename-safe ISO timestamp + PID string. */
47
47
  export function generateLogFilename() {
48
- const ts = new Date().toISOString().replace(/:/g, '-').replace(/\.\d{3}Z$/, '');
48
+ const ts = new Date()
49
+ .toISOString()
50
+ .replace(/:/g, '-')
51
+ .replace(/\.\d{3}Z$/, '');
49
52
  return `${ts}_${process.pid}.log`;
50
53
  }
51
54
  // ---------------------------------------------------------------------------
@@ -141,7 +144,9 @@ export class FileLogger {
141
144
  try {
142
145
  closeSync(this.fd);
143
146
  }
144
- catch { /* already closed */ }
147
+ catch {
148
+ /* already closed */
149
+ }
145
150
  this.fd = null;
146
151
  this.restoreConsole();
147
152
  }
@@ -191,9 +196,13 @@ export class FileLogger {
191
196
  try {
192
197
  writeSync(this.fd, stripAnsi(line) + '\n');
193
198
  }
194
- catch { /* fd may be invalid */ }
199
+ catch {
200
+ /* fd may be invalid */
201
+ }
195
202
  }
196
- onExit = () => { this.close(); };
203
+ onExit = () => {
204
+ this.close();
205
+ };
197
206
  registerShutdownHandlers() {
198
207
  process.once('SIGINT', this.onExit);
199
208
  process.once('SIGTERM', this.onExit);
package/dist/cli/index.js CHANGED
@@ -124,7 +124,8 @@ async function findAvailablePort(preferred) {
124
124
  }
125
125
  }
126
126
  function formatPreviewProperties(properties) {
127
- return properties.map((p) => {
127
+ return properties
128
+ .map((p) => {
128
129
  let val;
129
130
  if (p.type === 'object')
130
131
  val = p.subtype === 'array' ? '[...]' : '{...}';
@@ -133,7 +134,8 @@ function formatPreviewProperties(properties) {
133
134
  else
134
135
  val = p.value;
135
136
  return `${p.name}: ${val}`;
136
- }).join(', ');
137
+ })
138
+ .join(', ');
137
139
  }
138
140
  function formatRemoteObject(obj) {
139
141
  if (obj.type === 'undefined')
@@ -158,9 +160,12 @@ function formatRemoteObject(obj) {
158
160
  }
159
161
  function colorForType(type) {
160
162
  switch (type) {
161
- case 'error': return ANSI_RED;
162
- case 'warning': return ANSI_YELLOW;
163
- default: return ANSI_CYAN;
163
+ case 'error':
164
+ return ANSI_RED;
165
+ case 'warning':
166
+ return ANSI_YELLOW;
167
+ default:
168
+ return ANSI_CYAN;
164
169
  }
165
170
  }
166
171
  async function findPageTarget(cdpPort, pageUrl) {
@@ -225,7 +230,9 @@ async function attachConsoleForwarder(cdpPort, pageUrl) {
225
230
  });
226
231
  ws.on('close', () => {
227
232
  // Reconnect after a short delay (page may have reloaded)
228
- setTimeout(() => { connect(); }, 1000);
233
+ setTimeout(() => {
234
+ connect();
235
+ }, 1000);
229
236
  });
230
237
  ws.on('error', () => {
231
238
  // Error will trigger close, which handles reconnection
@@ -249,9 +256,7 @@ async function main() {
249
256
  // Electron mode keeps the preferred port (external CDP, not launched by us).
250
257
  const REQUESTED_CDP_PORT = ELECTRON_MODE ? PREFERRED_CDP_PORT : 0;
251
258
  let CDP_PORT = ELECTRON_MODE ? PREFERRED_CDP_PORT : 0;
252
- const HMR_PORT = DEV_MODE
253
- ? await findAvailablePort(PREFERRED_HMR_PORT)
254
- : PREFERRED_HMR_PORT;
259
+ const HMR_PORT = DEV_MODE ? await findAvailablePort(PREFERRED_HMR_PORT) : PREFERRED_HMR_PORT;
255
260
  const SERVE_ORIGIN = `http://localhost:${SERVE_PORT}`;
256
261
  if (SERVE_PORT !== PREFERRED_SERVE_PORT) {
257
262
  console.log(`Port ${PREFERRED_SERVE_PORT} in use, serving on port ${SERVE_PORT}`);
@@ -305,16 +310,18 @@ async function main() {
305
310
  const leaderOrigin = `http://localhost:${PREFERRED_SERVE_PORT}`;
306
311
  for (let attempt = 0; attempt < 5 && !discoveredTrayJoinUrl; attempt++) {
307
312
  try {
308
- const resp = await fetch(`${leaderOrigin}/api/tray-status`, { signal: AbortSignal.timeout(3000) });
313
+ const resp = await fetch(`${leaderOrigin}/api/tray-status`, {
314
+ signal: AbortSignal.timeout(3000),
315
+ });
309
316
  if (resp.ok) {
310
- const status = await resp.json();
317
+ const status = (await resp.json());
311
318
  if (status.joinUrl) {
312
319
  discoveredTrayJoinUrl = status.joinUrl;
313
320
  console.log(`Discovered leader tray join URL: ${status.joinUrl}`);
314
321
  }
315
322
  else if (status.state === 'connecting') {
316
323
  // Leader is still setting up — wait and retry
317
- await new Promise(r => setTimeout(r, 2000));
324
+ await new Promise((r) => setTimeout(r, 2000));
318
325
  }
319
326
  else {
320
327
  console.log(`Leader on port ${PREFERRED_SERVE_PORT} has no active tray (state: ${status.state ?? 'unknown'})`);
@@ -465,7 +472,7 @@ async function main() {
465
472
  const requestId = `req_${++requestIdCounter}`;
466
473
  const msg = JSON.stringify({ type, requestId, ...data });
467
474
  // Find a connected client
468
- const client = Array.from(lickClients).find(c => c.readyState === WebSocket.OPEN);
475
+ const client = Array.from(lickClients).find((c) => c.readyState === WebSocket.OPEN);
469
476
  if (!client) {
470
477
  reject(new Error('No browser connected'));
471
478
  return;
@@ -592,7 +599,7 @@ async function main() {
592
599
  });
593
600
  app.delete('/api/webhooks/:id', async (req, res) => {
594
601
  try {
595
- const data = await sendLickRequest('delete_webhook', { id: req.params.id });
602
+ const data = (await sendLickRequest('delete_webhook', { id: req.params.id }));
596
603
  if (data.error) {
597
604
  res.status(404).json({ error: data.error });
598
605
  }
@@ -659,12 +666,14 @@ async function main() {
659
666
  }
660
667
  catch (err) {
661
668
  const msg = err instanceof Error ? err.message : String(err);
662
- res.status(msg.includes('Invalid') || msg.includes('required') ? 400 : 503).json({ error: msg });
669
+ res
670
+ .status(msg.includes('Invalid') || msg.includes('required') ? 400 : 503)
671
+ .json({ error: msg });
663
672
  }
664
673
  });
665
674
  app.delete('/api/crontasks/:id', async (req, res) => {
666
675
  try {
667
- const data = await sendLickRequest('delete_crontask', { id: req.params.id });
676
+ const data = (await sendLickRequest('delete_crontask', { id: req.params.id }));
668
677
  if (data.error) {
669
678
  res.status(404).json({ error: data.error });
670
679
  }
@@ -705,7 +714,13 @@ async function main() {
705
714
  redirect: 'follow', // Follow redirects for git protocol compatibility
706
715
  };
707
716
  // Forward relevant headers (excluding hop-by-hop and proxy headers)
708
- const skipHeaders = new Set(['host', 'connection', 'x-target-url', 'content-length', 'transfer-encoding']);
717
+ const skipHeaders = new Set([
718
+ 'host',
719
+ 'connection',
720
+ 'x-target-url',
721
+ 'content-length',
722
+ 'transfer-encoding',
723
+ ]);
709
724
  const headers = {};
710
725
  for (const [key, value] of Object.entries(req.headers)) {
711
726
  if (!skipHeaders.has(key) && typeof value === 'string') {
@@ -732,11 +747,13 @@ async function main() {
732
747
  // handles 401s through its own onAuth callback)
733
748
  upstream.headers.forEach((v, k) => {
734
749
  const lower = k.toLowerCase();
735
- if (lower !== 'transfer-encoding' && lower !== 'content-encoding' && lower !== 'www-authenticate') {
750
+ if (lower !== 'transfer-encoding' &&
751
+ lower !== 'content-encoding' &&
752
+ lower !== 'www-authenticate') {
736
753
  res.setHeader(k, v);
737
754
  }
738
755
  });
739
- // Send body as raw binary - explicitly set content-length and use end()
756
+ // Send body as raw binary - explicitly set content-length and use end()
740
757
  // instead of send() to avoid any Express middleware transformations
741
758
  const body = await upstream.arrayBuffer();
742
759
  const buffer = Buffer.from(body);
@@ -815,14 +832,18 @@ async function main() {
815
832
  try {
816
833
  chromeWs.close();
817
834
  }
818
- catch { /* ignore */ }
835
+ catch {
836
+ /* ignore */
837
+ }
819
838
  chromeWs = null;
820
839
  }
821
840
  if (activeClientWs) {
822
841
  try {
823
842
  activeClientWs.close();
824
843
  }
825
- catch { /* ignore */ }
844
+ catch {
845
+ /* ignore */
846
+ }
826
847
  activeClientWs = null;
827
848
  }
828
849
  for (const client of wss.clients) {
@@ -833,7 +854,9 @@ async function main() {
833
854
  server.close();
834
855
  if (launchedBrowserProcess) {
835
856
  let browserExited = false;
836
- launchedBrowserProcess.on('exit', () => { browserExited = true; });
857
+ launchedBrowserProcess.on('exit', () => {
858
+ browserExited = true;
859
+ });
837
860
  try {
838
861
  const res = await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`);
839
862
  const json = (await res.json());
@@ -857,21 +880,29 @@ async function main() {
857
880
  try {
858
881
  launchedBrowserProcess.kill('SIGKILL');
859
882
  }
860
- catch { /* ignore */ }
883
+ catch {
884
+ /* ignore */
885
+ }
861
886
  }
862
887
  console.log(`${launchedBrowserLabel} closed`);
863
888
  }
864
889
  process.exit(0);
865
890
  };
866
- process.on('SIGINT', () => { gracefulShutdown(); });
867
- process.on('SIGTERM', () => { gracefulShutdown(); });
891
+ process.on('SIGINT', () => {
892
+ gracefulShutdown();
893
+ });
894
+ process.on('SIGTERM', () => {
895
+ gracefulShutdown();
896
+ });
868
897
  process.on('exit', () => {
869
898
  // Synchronous last-resort cleanup — kill the launched browser if it is still running.
870
899
  if (!shuttingDown && launchedBrowserProcess) {
871
900
  try {
872
901
  launchedBrowserProcess.kill();
873
902
  }
874
- catch { /* ignore */ }
903
+ catch {
904
+ /* ignore */
905
+ }
875
906
  }
876
907
  });
877
908
  function ensureChromeConnection(url) {
@@ -892,7 +923,9 @@ async function main() {
892
923
  try {
893
924
  chromeWs.close();
894
925
  }
895
- catch { /* ignore */ }
926
+ catch {
927
+ /* ignore */
928
+ }
896
929
  }
897
930
  messageBuffer = [];
898
931
  chromeWs = new WebSocket(url);
@@ -989,7 +1022,12 @@ async function main() {
989
1022
  server.listen(SERVE_PORT, '127.0.0.1', () => {
990
1023
  console.log(`Serving UI at ${SERVE_ORIGIN}`);
991
1024
  console.log(`CDP proxy at ws://localhost:${SERVE_PORT}/cdp`);
992
- fileLogger.log('info', 'CLI server started', { port: SERVE_PORT, cdpPort: CDP_PORT, devMode: DEV_MODE, electronMode: ELECTRON_MODE });
1025
+ fileLogger.log('info', 'CLI server started', {
1026
+ port: SERVE_PORT,
1027
+ cdpPort: CDP_PORT,
1028
+ devMode: DEV_MODE,
1029
+ electronMode: ELECTRON_MODE,
1030
+ });
993
1031
  // Pre-connect to Chrome's CDP so the proxy is warm when the first client connects.
994
1032
  // Without this, the first browser automation command has to wait for CDP discovery + WS handshake.
995
1033
  (async () => {
@@ -134,12 +134,10 @@ function crc32(buffer) {
134
134
  }
135
135
  function encodeDosDateTime(value) {
136
136
  const year = Math.max(value.getUTCFullYear(), 1980);
137
- const dosDate = ((year - 1980) << 9)
138
- | ((value.getUTCMonth() + 1) << 5)
139
- | value.getUTCDate();
140
- const dosTime = (value.getUTCHours() << 11)
141
- | (value.getUTCMinutes() << 5)
142
- | Math.floor(value.getUTCSeconds() / 2);
137
+ const dosDate = ((year - 1980) << 9) | ((value.getUTCMonth() + 1) << 5) | value.getUTCDate();
138
+ const dosTime = (value.getUTCHours() << 11) |
139
+ (value.getUTCMinutes() << 5) |
140
+ Math.floor(value.getUTCSeconds() / 2);
143
141
  return { dosDate, dosTime };
144
142
  }
145
143
  function readJsonFile(path) {
@@ -1 +1 @@
1
- import{_ as e}from"./__vite-browser-external-D7Ct-6yo.js";import{g as r}from"./index-C1lgMSN2.js";const a=r(e);export{a as r};
1
+ import{_ as e}from"./__vite-browser-external-D7Ct-6yo.js";import{g as r}from"./index-BOh9kRaE.js";const a=r(e);export{a as r};
@@ -1,2 +1,2 @@
1
- import{c as u}from"./index-C1lgMSN2.js";const l=["/workspace","/shared"];async function d(s){const t=[],e=new Set;for(const r of l)await s.exists(r)&&await p(s,r,t,e);return t}async function p(s,t,e,r){for await(const n of s.walk(t)){if(!n.endsWith(".bsh")||r.has(n))continue;r.add(n);const i=g(n);if(!i)continue;const a=await s.readFile(n,{encoding:"utf-8"}),c=typeof a=="string"?a:new TextDecoder().decode(a),f=m(c);e.push({path:n,hostnamePattern:i,matchPatterns:f})}}function g(s){const t=s.split("/").pop()??"";if(!t.endsWith(".bsh"))return null;const e=t.slice(0,-4);return e?e.startsWith("-.")?"*"+e.slice(1):e:null}function m(s){const t=s.split(`
1
+ import{c as u}from"./index-BOh9kRaE.js";const l=["/workspace","/shared"];async function d(s){const t=[],e=new Set;for(const r of l)await s.exists(r)&&await p(s,r,t,e);return t}async function p(s,t,e,r){for await(const n of s.walk(t)){if(!n.endsWith(".bsh")||r.has(n))continue;r.add(n);const i=g(n);if(!i)continue;const a=await s.readFile(n,{encoding:"utf-8"}),c=typeof a=="string"?a:new TextDecoder().decode(a),f=m(c);e.push({path:n,hostnamePattern:i,matchPatterns:f})}}function g(s){const t=s.split("/").pop()??"";if(!t.endsWith(".bsh"))return null;const e=t.slice(0,-4);return e?e.startsWith("-.")?"*"+e.slice(1):e:null}function m(s){const t=s.split(`
2
2
  `).slice(0,10),e=[];for(const r of t){const n=r.match(/^\s*\/\/\s*@match\s+(.+)$/);n&&e.push(n[1].trim())}return e}function h(s,t){if(t.startsWith("*.")){const e=t.slice(1);return s.endsWith(e)&&s.length>e.length}return s===t}function v(s,t){try{const e=new URL(s),r=t.match(/^(\*|https?):\/\/([^/]+)(\/.*)?$/);if(!r)return!1;const[,n,i,a]=r;return n!=="*"&&e.protocol.slice(0,-1)!==n||!h(e.hostname,i)?!1:a?x(e.pathname+e.search,a):!0}catch{return!1}}function x(s,t){const e="^"+t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")+"$";return new RegExp(e).test(s)}function w(s,t){try{const e=new URL(t);return s.filter(r=>h(e.hostname,r.hostnamePattern)?r.matchPatterns.length>0?r.matchPatterns.some(n=>v(t,n)):!0:!1)}catch{return[]}}const o=u("bsh-watchdog");class S{transport;fs;execute;discoveryIntervalMs;entries=[];discoveryTimer=null;running=!1;executing=new Set;constructor(t){this.transport=t.transport,this.fs=t.fs,this.execute=t.execute,this.discoveryIntervalMs=t.discoveryIntervalMs??3e4}async start(){this.running||(this.running=!0,await this.discover(),this.discoveryTimer=setInterval(()=>{this.discover()},this.discoveryIntervalMs),this.transport.on("Page.frameNavigated",this.onFrameNavigated),o.info("BSH watchdog started",{scriptCount:this.entries.length}))}stop(){this.running&&(this.running=!1,this.transport.off("Page.frameNavigated",this.onFrameNavigated),this.discoveryTimer&&(clearInterval(this.discoveryTimer),this.discoveryTimer=null),this.entries=[],this.executing.clear(),o.info("BSH watchdog stopped"))}async discover(){try{this.entries=await d(this.fs),o.debug("BSH discovery complete",{count:this.entries.length})}catch(t){o.error("BSH discovery failed",{error:t instanceof Error?t.message:String(t)})}}getEntries(){return this.entries}onFrameNavigated=t=>{const e=t.frame;if(e?.parentId||!e?.url)return;const r=e.url;if(!r.startsWith("http://")&&!r.startsWith("https://")||this.entries.length===0)return;const n=w(this.entries,r);if(n.length!==0)for(const i of n){const a=`${i.path}::${r}`;this.executing.has(a)||(this.executing.add(a),o.info("BSH watchdog executing script",{script:i.path,url:r}),this.execute(i.path).then(c=>{c.exitCode!==0?o.warn("BSH script failed",{script:i.path,url:r,exitCode:c.exitCode,stderr:c.stderr.slice(0,200)}):o.info("BSH script completed",{script:i.path,url:r})}).catch(c=>{o.error("BSH script execution error",{script:i.path,url:r,error:c instanceof Error?c.message:String(c)})}).finally(()=>{this.executing.delete(a)}))}}}export{S as BshWatchdog};