sliccy 1.4.1 → 1.6.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 +13 -3
- package/dist/cli/electron-controller.d.ts +14 -0
- package/dist/cli/electron-controller.js +220 -29
- package/dist/cli/electron-main.js +5 -2
- package/dist/ui/assets/{___vite-browser-external_commonjs-proxy-Dfz2nQpt.js → ___vite-browser-external_commonjs-proxy-Dmy7YA9d.js} +1 -1
- package/dist/ui/assets/{bsh-watchdog-Bg5KAOQZ.js → bsh-watchdog-Dor44V0O.js} +1 -1
- package/dist/ui/assets/{index-CbuY8SZ0.js → index-8BrOrSsx.js} +1 -1
- package/dist/ui/assets/{index-B4khf8fB.js → index-BLgzA5P5.js} +1 -1
- package/dist/ui/assets/{index-Bk00i2d6.js → index-BMX9qDja.js} +1 -1
- package/dist/ui/assets/{index-DLbRRCUW.js → index-C1tZMpET.js} +2259 -1602
- package/dist/ui/assets/{index-DXoH0djJ.js → index-CCawsuAn.js} +1 -1
- package/dist/ui/assets/{index-BhkUx2Bt.js → index-CE-zqWqs.js} +1 -1
- package/dist/ui/assets/{index-C9dx-Iqm.js → index-CfkLdZHp.js} +1 -1
- package/dist/ui/assets/{index-CM00ez50.js → index-Cwh84rC2.js} +1 -1
- package/dist/ui/assets/index-D2mLc9tI.css +1 -0
- package/dist/ui/assets/{index-BZBTT_8U.js → index-DtWhmY9J.js} +1 -1
- package/dist/ui/assets/{offscreen-client-BXEKsuSZ.js → offscreen-client-BsZ13Pkt.js} +1 -1
- package/dist/ui/assets/{sql-wasm-D2FbW-kH.js → sql-wasm-CBrEnfnW.js} +1 -1
- package/dist/ui/index.html +2 -1969
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-

|
|
2
|
+
|
|
3
|
+
You are looking at a macOS desktop, with four windows running:
|
|
4
|
+
|
|
5
|
+
1. Google Chrome, running SLICC as a web application. It shows a Welcome page, a hidden tab with meeting preparation notes that were created by the agent, and a terminal, showing that the operating system is of the unlikely `Mozilla/5.0` kind. What?
|
|
6
|
+
2. Slack, the desktop app. Err. Slack the Electron app. It has on overlay injected, showing the ice cream logo asking to join a tray. If you do this, Slack can be remote-controlled by your agent. What the?
|
|
7
|
+
3. Sliccstart, the desktop app. It's an actual macOS app, but one that controls browsers, and browsers that prentend to be native apps alike. What the ice cream?
|
|
8
|
+
4. An image of an antropomorphized ice cream cone made out of felt and googly eyes. It's sticking out its tongue, half in astonishment, half in anticipation. What the ice cream truck?
|
|
9
|
+
|
|
10
|
+
If this scares, confuses, or excites you, keep reading.
|
|
2
11
|
|
|
3
12
|
# slicc — Self-Licking Ice Cream Cone
|
|
4
13
|
|
|
@@ -8,13 +17,14 @@
|
|
|
8
17
|
|
|
9
18
|
SLICC runs in a browser and controls the browser it runs in. It combines a shell, files, browser automation, and multi-agent delegation so you can do real work from one workspace — coding, web automation, authenticated app tasks, and the weird in-between jobs that do not fit neatly inside a chat panel. SLICC can orchestrate multiple browsers, and even some apps through telepathy, making it a powerful hub for your digital work.
|
|
10
19
|
|
|
11
|
-
-
|
|
20
|
+
- Head over to [releases](https://github.com/ai-ecoverse/slicc/releases) and grab the latest `.dmg` file. No Windows or Linux UI yet
|
|
21
|
+
- Or launch it from the CLI today (we also have a Chrome extension)
|
|
12
22
|
- Connect other browser windows or Electron apps
|
|
13
23
|
- Install skills that teach it how to perform challenging tasks
|
|
14
24
|
- Give it practical tools models already know how to use
|
|
15
25
|
- Delegate parallel work so tasks get done faster
|
|
16
26
|
|
|
17
|
-
> Status: active working prototype. The
|
|
27
|
+
> Status: active working prototype. The macOS app is the easiest way in today; and we have submitted the extension to Chrome Web Store.
|
|
18
28
|
|
|
19
29
|
## Why SLICC is different
|
|
20
30
|
|
|
@@ -17,6 +17,20 @@ export declare function launchElectronApp(options: {
|
|
|
17
17
|
child: ChildProcess;
|
|
18
18
|
displayName: string;
|
|
19
19
|
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Decode a base64 PNG into raw RGBA pixel data by parsing chunks and inflating.
|
|
22
|
+
* Returns { width, height, pixels } where pixels is a Buffer of RGBA bytes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decodePngPixels(base64Data: string): {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
pixels: Buffer;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Compute the average perceived luminance (0–255) from RGBA pixel data,
|
|
31
|
+
* sampling a grid of pixels for performance.
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeAverageLuminance(pixels: Buffer, width: number, height: number, sampleStep?: number): number;
|
|
20
34
|
export declare class ElectronOverlayInjector {
|
|
21
35
|
private readonly cdpPort;
|
|
22
36
|
private readonly bootstrapScript;
|
|
@@ -4,6 +4,7 @@ import { readFile } from 'fs/promises';
|
|
|
4
4
|
import * as http from 'http';
|
|
5
5
|
import * as https from 'https';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
|
+
import { inflateSync } from 'zlib';
|
|
7
8
|
import { WebSocket } from 'ws';
|
|
8
9
|
import { buildElectronAppLaunchSpec, buildElectronOverlayAppUrl, buildElectronOverlayBootstrapScript, buildElectronOverlayEntryUrl, getElectronOverlayEntryDistPath, getElectronServeOrigin, selectBestOverlayTargets, } from './electron-runtime.js';
|
|
9
10
|
const execFile = promisify(nodeExecFile);
|
|
@@ -170,6 +171,175 @@ export async function launchElectronApp(options) {
|
|
|
170
171
|
displayName: launchSpec.displayName,
|
|
171
172
|
};
|
|
172
173
|
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Theme detection — screenshot-based luminance analysis
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
/**
|
|
178
|
+
* Decode a base64 PNG into raw RGBA pixel data by parsing chunks and inflating.
|
|
179
|
+
* Returns { width, height, pixels } where pixels is a Buffer of RGBA bytes.
|
|
180
|
+
*/
|
|
181
|
+
export function decodePngPixels(base64Data) {
|
|
182
|
+
const buf = Buffer.from(base64Data, 'base64');
|
|
183
|
+
// Validate PNG signature
|
|
184
|
+
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
185
|
+
if (buf.subarray(0, 8).compare(PNG_SIGNATURE) !== 0) {
|
|
186
|
+
throw new Error('Not a valid PNG');
|
|
187
|
+
}
|
|
188
|
+
let width = 0;
|
|
189
|
+
let height = 0;
|
|
190
|
+
let bitDepth = 0;
|
|
191
|
+
let colorType = 0;
|
|
192
|
+
const idatChunks = [];
|
|
193
|
+
let offset = 8;
|
|
194
|
+
while (offset < buf.length) {
|
|
195
|
+
const chunkLength = buf.readUInt32BE(offset);
|
|
196
|
+
const chunkType = buf.subarray(offset + 4, offset + 8).toString('ascii');
|
|
197
|
+
const chunkData = buf.subarray(offset + 8, offset + 8 + chunkLength);
|
|
198
|
+
if (chunkType === 'IHDR') {
|
|
199
|
+
width = chunkData.readUInt32BE(0);
|
|
200
|
+
height = chunkData.readUInt32BE(4);
|
|
201
|
+
bitDepth = chunkData[8];
|
|
202
|
+
colorType = chunkData[9];
|
|
203
|
+
}
|
|
204
|
+
else if (chunkType === 'IDAT') {
|
|
205
|
+
idatChunks.push(chunkData);
|
|
206
|
+
}
|
|
207
|
+
else if (chunkType === 'IEND') {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
offset += 12 + chunkLength; // 4 (length) + 4 (type) + data + 4 (CRC)
|
|
211
|
+
}
|
|
212
|
+
if (width === 0 || height === 0)
|
|
213
|
+
throw new Error('Missing IHDR chunk');
|
|
214
|
+
if (bitDepth !== 8)
|
|
215
|
+
throw new Error(`Unsupported bit depth: ${bitDepth}`);
|
|
216
|
+
// Only support RGB (2) and RGBA (6) — CDP screenshots are always RGBA
|
|
217
|
+
const bytesPerPixel = colorType === 6 ? 4 : colorType === 2 ? 3 : 0;
|
|
218
|
+
if (bytesPerPixel === 0)
|
|
219
|
+
throw new Error(`Unsupported color type: ${colorType}`);
|
|
220
|
+
const compressed = Buffer.concat(idatChunks);
|
|
221
|
+
const inflated = inflateSync(compressed);
|
|
222
|
+
// Each row has a 1-byte filter prefix followed by pixel data
|
|
223
|
+
const rowBytes = width * bytesPerPixel;
|
|
224
|
+
const pixels = Buffer.alloc(width * height * 4); // Always output RGBA
|
|
225
|
+
let prevRow = Buffer.alloc(rowBytes);
|
|
226
|
+
for (let y = 0; y < height; y++) {
|
|
227
|
+
const rowStart = y * (1 + rowBytes);
|
|
228
|
+
const filter = inflated[rowStart];
|
|
229
|
+
const row = Buffer.from(inflated.subarray(rowStart + 1, rowStart + 1 + rowBytes));
|
|
230
|
+
// Apply PNG row filters
|
|
231
|
+
for (let i = 0; i < rowBytes; i++) {
|
|
232
|
+
const a = i >= bytesPerPixel ? row[i - bytesPerPixel] : 0;
|
|
233
|
+
const b = prevRow[i];
|
|
234
|
+
const c = i >= bytesPerPixel ? prevRow[i - bytesPerPixel] : 0;
|
|
235
|
+
switch (filter) {
|
|
236
|
+
case 1: // Sub
|
|
237
|
+
row[i] = (row[i] + a) & 0xff;
|
|
238
|
+
break;
|
|
239
|
+
case 2: // Up
|
|
240
|
+
row[i] = (row[i] + b) & 0xff;
|
|
241
|
+
break;
|
|
242
|
+
case 3: // Average
|
|
243
|
+
row[i] = (row[i] + ((a + b) >>> 1)) & 0xff;
|
|
244
|
+
break;
|
|
245
|
+
case 4: { // Paeth
|
|
246
|
+
const p = a + b - c;
|
|
247
|
+
const pa = Math.abs(p - a);
|
|
248
|
+
const pb = Math.abs(p - b);
|
|
249
|
+
const pc = Math.abs(p - c);
|
|
250
|
+
row[i] = (row[i] + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c)) & 0xff;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
// case 0: None — no transformation needed
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (let x = 0; x < width; x++) {
|
|
257
|
+
const srcIdx = x * bytesPerPixel;
|
|
258
|
+
const dstIdx = (y * width + x) * 4;
|
|
259
|
+
pixels[dstIdx] = row[srcIdx]; // R
|
|
260
|
+
pixels[dstIdx + 1] = row[srcIdx + 1]; // G
|
|
261
|
+
pixels[dstIdx + 2] = row[srcIdx + 2]; // B
|
|
262
|
+
pixels[dstIdx + 3] = bytesPerPixel === 4 ? row[srcIdx + 3] : 255; // A
|
|
263
|
+
}
|
|
264
|
+
prevRow = row;
|
|
265
|
+
}
|
|
266
|
+
return { width, height, pixels };
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Compute the average perceived luminance (0–255) from RGBA pixel data,
|
|
270
|
+
* sampling a grid of pixels for performance.
|
|
271
|
+
*/
|
|
272
|
+
export function computeAverageLuminance(pixels, width, height, sampleStep = 4) {
|
|
273
|
+
let totalLuminance = 0;
|
|
274
|
+
let sampleCount = 0;
|
|
275
|
+
for (let y = 0; y < height; y += sampleStep) {
|
|
276
|
+
for (let x = 0; x < width; x += sampleStep) {
|
|
277
|
+
const idx = (y * width + x) * 4;
|
|
278
|
+
const r = pixels[idx];
|
|
279
|
+
const g = pixels[idx + 1];
|
|
280
|
+
const b = pixels[idx + 2];
|
|
281
|
+
// ITU-R BT.601 perceived luminance
|
|
282
|
+
totalLuminance += 0.299 * r + 0.587 * g + 0.114 * b;
|
|
283
|
+
sampleCount++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return sampleCount > 0 ? totalLuminance / sampleCount : 128;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Detect whether the target app is using a light or dark theme by taking
|
|
290
|
+
* a CDP screenshot and analyzing the average luminance.
|
|
291
|
+
* Returns 'light' or 'dark'.
|
|
292
|
+
*/
|
|
293
|
+
function detectAppThemeFromScreenshot(ws, send) {
|
|
294
|
+
return new Promise((resolve) => {
|
|
295
|
+
// Take a small JPEG screenshot for speed — we only need luminance
|
|
296
|
+
const screenshotId = send('Page.captureScreenshot', {
|
|
297
|
+
format: 'png',
|
|
298
|
+
quality: 30,
|
|
299
|
+
clip: { x: 0, y: 0, width: 160, height: 120, scale: 0.25 },
|
|
300
|
+
optimizeForSpeed: true,
|
|
301
|
+
});
|
|
302
|
+
const timeout = setTimeout(() => {
|
|
303
|
+
cleanup();
|
|
304
|
+
console.log('[electron-float] Theme detection timed out, defaulting to dark');
|
|
305
|
+
resolve('dark');
|
|
306
|
+
}, 5000);
|
|
307
|
+
const onMessage = (data) => {
|
|
308
|
+
try {
|
|
309
|
+
const msg = JSON.parse(data.toString());
|
|
310
|
+
if (msg.id !== screenshotId)
|
|
311
|
+
return;
|
|
312
|
+
cleanup();
|
|
313
|
+
const base64 = msg.result?.data;
|
|
314
|
+
if (!base64) {
|
|
315
|
+
console.log('[electron-float] Theme detection: no screenshot data, defaulting to dark');
|
|
316
|
+
resolve('dark');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
const { width, height, pixels } = decodePngPixels(base64);
|
|
321
|
+
const luminance = computeAverageLuminance(pixels, width, height);
|
|
322
|
+
const theme = luminance > 128 ? 'light' : 'dark';
|
|
323
|
+
console.log(`[electron-float] Theme detection: luminance=${luminance.toFixed(1)}, theme=${theme} (${width}x${height})`);
|
|
324
|
+
resolve(theme);
|
|
325
|
+
}
|
|
326
|
+
catch (decodeError) {
|
|
327
|
+
const message = decodeError instanceof Error ? decodeError.message : String(decodeError);
|
|
328
|
+
console.error('[electron-float] Theme detection decode failed:', message);
|
|
329
|
+
resolve('dark');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
/* ignore non-JSON messages */
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const cleanup = () => {
|
|
337
|
+
clearTimeout(timeout);
|
|
338
|
+
ws.off('message', onMessage);
|
|
339
|
+
};
|
|
340
|
+
ws.on('message', onMessage);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
173
343
|
async function loadElectronOverlayBundleSource(options) {
|
|
174
344
|
const serveOrigin = getElectronServeOrigin(options.servePort);
|
|
175
345
|
if (options.dev) {
|
|
@@ -323,6 +493,14 @@ export class ElectronOverlayInjector {
|
|
|
323
493
|
let pendingReload = false;
|
|
324
494
|
let pendingCspEscalation = false;
|
|
325
495
|
let fetchProxyActive = false;
|
|
496
|
+
/**
|
|
497
|
+
* Build a script that sets the SLICC theme preference in localStorage
|
|
498
|
+
* to match the target app's detected theme, then runs the bootstrap.
|
|
499
|
+
*/
|
|
500
|
+
const buildThemedBootstrap = (theme) => {
|
|
501
|
+
const themeScript = `try{localStorage.setItem('slicc-theme',${JSON.stringify(theme)})}catch(e){}`;
|
|
502
|
+
return `${themeScript}\n${bootstrapScript}`;
|
|
503
|
+
};
|
|
326
504
|
ws.on('open', () => {
|
|
327
505
|
const isWebContent = target.url.startsWith('https://');
|
|
328
506
|
const alreadyBypassed = cspBypassedTargets.has(target.url);
|
|
@@ -332,39 +510,48 @@ export class ElectronOverlayInjector {
|
|
|
332
510
|
// Set CSP bypass — affects future resource loads on the current page
|
|
333
511
|
send('Page.setBypassCSP', { enabled: true });
|
|
334
512
|
if (alreadyBypassed) {
|
|
335
|
-
// Already reloaded with CSP bypass previously —
|
|
336
|
-
console.log(`[electron-float]
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
console.log(`[electron-float] Injecting overlay (first attempt)...`);
|
|
343
|
-
send('Runtime.evaluate', { expression: bootstrapScript, awaitPromise: false });
|
|
344
|
-
if (!isWebContent) {
|
|
345
|
-
// Local content (file://, app protocol) — CSP is not an issue
|
|
513
|
+
// Already reloaded with CSP bypass previously — detect theme and inject
|
|
514
|
+
console.log(`[electron-float] Detecting theme and injecting overlay (CSP already bypassed)...`);
|
|
515
|
+
void detectAppThemeFromScreenshot(ws, send).then((theme) => {
|
|
516
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
517
|
+
return;
|
|
518
|
+
send('Runtime.evaluate', { expression: buildThemedBootstrap(theme), awaitPromise: false });
|
|
519
|
+
});
|
|
346
520
|
return;
|
|
347
521
|
}
|
|
348
|
-
//
|
|
349
|
-
// If CSP blocked it,
|
|
350
|
-
|
|
351
|
-
|
|
522
|
+
// First connection to this target URL: detect theme, then inject overlay.
|
|
523
|
+
// After injection, check if the iframe loaded. If CSP blocked it, fall back to reload+proxy.
|
|
524
|
+
console.log(`[electron-float] Detecting theme before first overlay injection...`);
|
|
525
|
+
void detectAppThemeFromScreenshot(ws, send).then((theme) => {
|
|
352
526
|
if (ws.readyState !== WebSocket.OPEN)
|
|
353
527
|
return;
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
528
|
+
console.log(`[electron-float] Injecting overlay (first attempt, theme=${theme})...`);
|
|
529
|
+
send('Runtime.evaluate', { expression: buildThemedBootstrap(theme), awaitPromise: false });
|
|
530
|
+
if (!isWebContent) {
|
|
531
|
+
// Local content (file://, app protocol) — CSP is not an issue
|
|
358
532
|
return;
|
|
359
533
|
}
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
534
|
+
// After a short delay, probe whether the overlay iframe loaded.
|
|
535
|
+
// If CSP blocked it, reload the page so Page.setBypassCSP takes effect.
|
|
536
|
+
// If that still doesn't work, escalate to the Fetch proxy.
|
|
537
|
+
setTimeout(async () => {
|
|
538
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
539
|
+
return;
|
|
540
|
+
const loaded = await this.probeOverlayIframeLoaded(ws, send);
|
|
541
|
+
if (loaded) {
|
|
542
|
+
console.log(`[electron-float] Overlay iframe loaded successfully — no CSP reload needed`);
|
|
543
|
+
cspBypassedTargets.add(target.url);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// Phase 2: Page.setBypassCSP was already set — a simple reload should
|
|
547
|
+
// make the browser ignore CSP headers on the fresh navigation.
|
|
548
|
+
console.log(`[electron-float] Overlay iframe blocked by CSP, reloading with bypass: ${target.url}`);
|
|
549
|
+
cspBypassedTargets.add(target.url);
|
|
550
|
+
pendingReload = true;
|
|
551
|
+
pendingCspEscalation = true;
|
|
552
|
+
send('Page.reload', { ignoreCache: true });
|
|
553
|
+
}, 1500);
|
|
554
|
+
});
|
|
368
555
|
});
|
|
369
556
|
// Handle CDP events: lifecycle events and Fetch interception
|
|
370
557
|
ws.on('message', (data) => {
|
|
@@ -373,10 +560,14 @@ export class ElectronOverlayInjector {
|
|
|
373
560
|
// Inject overlay after page load completes (after CSP-bypass reload)
|
|
374
561
|
if (msg.method === 'Page.loadEventFired' && pendingReload) {
|
|
375
562
|
pendingReload = false;
|
|
376
|
-
console.log(`[electron-float] Page loaded after CSP reload, injecting overlay...`);
|
|
563
|
+
console.log(`[electron-float] Page loaded after CSP reload, detecting theme and injecting overlay...`);
|
|
377
564
|
if (ws.readyState !== WebSocket.OPEN)
|
|
378
565
|
return;
|
|
379
|
-
|
|
566
|
+
void detectAppThemeFromScreenshot(ws, send).then((theme) => {
|
|
567
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
568
|
+
return;
|
|
569
|
+
send('Runtime.evaluate', { expression: buildThemedBootstrap(theme), awaitPromise: false });
|
|
570
|
+
});
|
|
380
571
|
// If this was a simple reload (no proxy), check if the iframe loads now.
|
|
381
572
|
// If it still doesn't, escalate to the Fetch proxy as a last resort.
|
|
382
573
|
if (pendingCspEscalation) {
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'child_process';
|
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
3
|
import { resolve } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
-
import { app, BrowserWindow, session } from 'electron';
|
|
5
|
+
import { app, BrowserWindow, nativeTheme, session } from 'electron';
|
|
6
6
|
import { buildElectronOverlayAppUrl, buildElectronOverlayEntryUrl, buildElectronOverlayInjectionCall, buildElectronServerSpawnConfig, getElectronOverlayEntryDistPath, getElectronServeOrigin, parseElectronFloatFlags, } from './electron-runtime.js';
|
|
7
7
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
8
8
|
const PROJECT_ROOT = resolve(__dirname, '..', '..');
|
|
@@ -47,7 +47,10 @@ async function loadOverlayBundleSource() {
|
|
|
47
47
|
}
|
|
48
48
|
async function injectOverlay(window) {
|
|
49
49
|
const bundleSource = await loadOverlayBundleSource();
|
|
50
|
-
|
|
50
|
+
// Detect the app's effective theme and set SLICC's theme to match
|
|
51
|
+
const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
|
|
52
|
+
const themeScript = `try{localStorage.setItem('slicc-theme',${JSON.stringify(theme)})}catch(e){}`;
|
|
53
|
+
await window.webContents.executeJavaScript(`${themeScript}\n${bundleSource}`, true);
|
|
51
54
|
await window.webContents.executeJavaScript(buildElectronOverlayInjectionCall({ appUrl: OVERLAY_APP_URL }), true);
|
|
52
55
|
}
|
|
53
56
|
function wireOverlayReinjection(window) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as e}from"./__vite-browser-external-D7Ct-6yo.js";import{g as r}from"./index-
|
|
1
|
+
import{_ as e}from"./__vite-browser-external-D7Ct-6yo.js";import{g as r}from"./index-C1tZMpET.js";const a=r(e);export{a as r};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{c as u}from"./index-
|
|
1
|
+
import{c as u}from"./index-C1tZMpET.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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as z,f as x}from"./index-
|
|
1
|
+
import{t as z,f as x}from"./index-C1tZMpET.js";class T{marshaller;serializer;deserializer;serdeContext;defaultContentType;constructor({marshaller:i,serializer:n,deserializer:o,serdeContext:c,defaultContentType:y}){this.marshaller=i,this.serializer=n,this.deserializer=o,this.serdeContext=c,this.defaultContentType=y}async serializeEventStream({eventStream:i,requestSchema:n,initialRequest:o}){const c=this.marshaller,y=n.getEventStreamMember(),p=n.getMemberSchema(y),d=this.serializer,u=this.defaultContentType,h=Symbol("initialRequestMarker"),S={async*[Symbol.asyncIterator](){if(o){const r={":event-type":{type:"string",value:"initial-request"},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:u}};d.write(n,o);const t=d.flush();yield{[h]:!0,headers:r,body:t}}for await(const r of i)yield r}};return c.serialize(S,r=>{if(r[h])return{headers:r.headers,body:r.body};const t=Object.keys(r).find(s=>s!=="__type")??"",{additionalHeaders:e,body:a,eventType:l,explicitPayloadContentType:m}=this.writeEventBody(t,p,r);return{headers:{":event-type":{type:"string",value:l},":message-type":{type:"string",value:"event"},":content-type":{type:"string",value:m??u},...e},body:a}})}async deserializeEventStream({response:i,responseSchema:n,initialResponseContainer:o}){const c=this.marshaller,y=n.getEventStreamMember(),d=n.getMemberSchema(y).getMemberSchemas(),u=Symbol("initialResponseMarker"),h=c.deserialize(i.body,async t=>{const e=Object.keys(t).find(l=>l!=="__type")??"",a=t[e].body;if(e==="initial-response"){const l=await this.deserializer.read(n,a);return delete l[y],{[u]:!0,...l}}else if(e in d){const l=d[e];if(l.isStructSchema()){const m={};let f=!1;for(const[s,g]of l.structIterator()){const{eventHeader:v,eventPayload:w}=g.getMergedTraits();if(f=f||!!(v||w),w)g.isBlobSchema()?m[s]=a:g.isStringSchema()?m[s]=(this.serdeContext?.utf8Encoder??z)(a):g.isStructSchema()&&(m[s]=await this.deserializer.read(g,a));else if(v){const b=t[e].headers[s]?.value;b!=null&&(g.isNumericSchema()?b&&typeof b=="object"&&"bytes"in b?m[s]=BigInt(b.toString()):m[s]=Number(b):m[s]=b)}}if(f)return{[e]:m};if(a.byteLength===0)return{[e]:{}}}return{[e]:await this.deserializer.read(l,a)}}else return{$unknown:t}}),S=h[Symbol.asyncIterator](),r=await S.next();if(r.done)return h;if(r.value?.[u]){if(!n)throw new Error("@smithy::core/protocols - initial-response event encountered in event stream but no response schema given.");for(const[t,e]of Object.entries(r.value))o[t]=e}return{async*[Symbol.asyncIterator](){for(r?.value?.[u]||(yield r.value);;){const{done:t,value:e}=await S.next();if(t)break;yield e}}}}writeEventBody(i,n,o){const c=this.serializer;let y=i,p=null,d;const u=n.getSchema()[4].includes(i),h={};if(u){const t=n.getMemberSchema(i);if(t.isStructSchema()){for(const[e,a]of t.structIterator()){const{eventHeader:l,eventPayload:m}=a.getMergedTraits();if(m)p=e;else if(l){const f=o[i][e];let s="binary";a.isNumericSchema()?(-2)**31<=f&&f<=2**31-1?s="integer":s="long":a.isTimestampSchema()?s="timestamp":a.isStringSchema()?s="string":a.isBooleanSchema()&&(s="boolean"),f!=null&&(h[e]={type:s,value:f},delete o[i][e])}}if(p!==null){const e=t.getMemberSchema(p);e.isBlobSchema()?d="application/octet-stream":e.isStringSchema()&&(d="text/plain"),c.write(e,o[i][p])}else c.write(t,o[i])}else if(t.isUnitSchema())c.write(t,{});else throw new Error("@smithy/core/event-streams - non-struct member not supported in event stream union.")}else{const[t,e]=o[i];y=t,c.write(15,e)}const S=c.flush();return{body:typeof S=="string"?(this.serdeContext?.utf8Decoder??x)(S):S,eventType:y,explicitPayloadContentType:d,additionalHeaders:h}}}export{T as EventStreamSerde};
|