mitsupi 1.0.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.
Files changed (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +95 -0
  3. package/TODO.md +11 -0
  4. package/commands/handoff.md +100 -0
  5. package/commands/make-release.md +75 -0
  6. package/commands/pickup.md +30 -0
  7. package/commands/update-changelog.md +78 -0
  8. package/package.json +22 -0
  9. package/pi-extensions/answer.ts +527 -0
  10. package/pi-extensions/codex-tuning.ts +632 -0
  11. package/pi-extensions/commit.ts +248 -0
  12. package/pi-extensions/cwd-history.ts +237 -0
  13. package/pi-extensions/issues.ts +548 -0
  14. package/pi-extensions/loop.ts +446 -0
  15. package/pi-extensions/qna.ts +167 -0
  16. package/pi-extensions/reveal.ts +689 -0
  17. package/pi-extensions/review.ts +807 -0
  18. package/pi-themes/armin.json +81 -0
  19. package/pi-themes/nightowl.json +82 -0
  20. package/skills/anachb/SKILL.md +183 -0
  21. package/skills/anachb/departures.sh +79 -0
  22. package/skills/anachb/disruptions.sh +53 -0
  23. package/skills/anachb/route.sh +87 -0
  24. package/skills/anachb/search.sh +43 -0
  25. package/skills/ghidra/SKILL.md +254 -0
  26. package/skills/ghidra/scripts/find-ghidra.sh +54 -0
  27. package/skills/ghidra/scripts/ghidra-analyze.sh +239 -0
  28. package/skills/ghidra/scripts/ghidra_scripts/ExportAll.java +278 -0
  29. package/skills/ghidra/scripts/ghidra_scripts/ExportCalls.java +148 -0
  30. package/skills/ghidra/scripts/ghidra_scripts/ExportDecompiled.java +84 -0
  31. package/skills/ghidra/scripts/ghidra_scripts/ExportFunctions.java +114 -0
  32. package/skills/ghidra/scripts/ghidra_scripts/ExportStrings.java +123 -0
  33. package/skills/ghidra/scripts/ghidra_scripts/ExportSymbols.java +135 -0
  34. package/skills/github/SKILL.md +47 -0
  35. package/skills/improve-skill/SKILL.md +155 -0
  36. package/skills/improve-skill/scripts/extract-session.js +349 -0
  37. package/skills/oebb-scotty/SKILL.md +429 -0
  38. package/skills/oebb-scotty/arrivals.sh +83 -0
  39. package/skills/oebb-scotty/departures.sh +83 -0
  40. package/skills/oebb-scotty/disruptions.sh +33 -0
  41. package/skills/oebb-scotty/search-station.sh +36 -0
  42. package/skills/oebb-scotty/trip.sh +119 -0
  43. package/skills/openscad/SKILL.md +232 -0
  44. package/skills/openscad/examples/parametric_box.scad +92 -0
  45. package/skills/openscad/examples/phone_stand.scad +95 -0
  46. package/skills/openscad/tools/common.sh +50 -0
  47. package/skills/openscad/tools/export-stl.sh +56 -0
  48. package/skills/openscad/tools/extract-params.sh +147 -0
  49. package/skills/openscad/tools/multi-preview.sh +68 -0
  50. package/skills/openscad/tools/preview.sh +74 -0
  51. package/skills/openscad/tools/render-with-params.sh +91 -0
  52. package/skills/openscad/tools/validate.sh +46 -0
  53. package/skills/pi-share/SKILL.md +105 -0
  54. package/skills/pi-share/fetch-session.mjs +322 -0
  55. package/skills/sentry/SKILL.md +239 -0
  56. package/skills/sentry/lib/auth.js +99 -0
  57. package/skills/sentry/scripts/fetch-event.js +329 -0
  58. package/skills/sentry/scripts/fetch-issue.js +356 -0
  59. package/skills/sentry/scripts/list-issues.js +239 -0
  60. package/skills/sentry/scripts/search-events.js +291 -0
  61. package/skills/sentry/scripts/search-logs.js +240 -0
  62. package/skills/tmux/SKILL.md +105 -0
  63. package/skills/tmux/scripts/find-sessions.sh +112 -0
  64. package/skills/tmux/scripts/wait-for-text.sh +83 -0
  65. package/skills/web-browser/SKILL.md +91 -0
  66. package/skills/web-browser/scripts/cdp.js +210 -0
  67. package/skills/web-browser/scripts/dismiss-cookies.js +373 -0
  68. package/skills/web-browser/scripts/eval.js +68 -0
  69. package/skills/web-browser/scripts/logs-tail.js +69 -0
  70. package/skills/web-browser/scripts/nav.js +65 -0
  71. package/skills/web-browser/scripts/net-summary.js +94 -0
  72. package/skills/web-browser/scripts/package-lock.json +33 -0
  73. package/skills/web-browser/scripts/package.json +6 -0
  74. package/skills/web-browser/scripts/pick.js +165 -0
  75. package/skills/web-browser/scripts/screenshot.js +52 -0
  76. package/skills/web-browser/scripts/start.js +80 -0
  77. package/skills/web-browser/scripts/watch.js +266 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Minimal CDP client - no puppeteer, no hangs
3
+ */
4
+
5
+ import WebSocket from "ws";
6
+
7
+ export async function connect(timeout = 5000) {
8
+ const controller = new AbortController();
9
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
10
+
11
+ try {
12
+ const resp = await fetch("http://localhost:9222/json/version", {
13
+ signal: controller.signal,
14
+ });
15
+ const { webSocketDebuggerUrl } = await resp.json();
16
+ clearTimeout(timeoutId);
17
+
18
+ return new Promise((resolve, reject) => {
19
+ const ws = new WebSocket(webSocketDebuggerUrl);
20
+ const connectTimeout = setTimeout(() => {
21
+ ws.close();
22
+ reject(new Error("WebSocket connect timeout"));
23
+ }, timeout);
24
+
25
+ ws.on("open", () => {
26
+ clearTimeout(connectTimeout);
27
+ resolve(new CDP(ws));
28
+ });
29
+ ws.on("error", (e) => {
30
+ clearTimeout(connectTimeout);
31
+ reject(e);
32
+ });
33
+ });
34
+ } catch (e) {
35
+ clearTimeout(timeoutId);
36
+ if (e.name === "AbortError") {
37
+ throw new Error("Connection timeout - is Chrome running with --remote-debugging-port=9222?");
38
+ }
39
+ throw e;
40
+ }
41
+ }
42
+
43
+ class CDP {
44
+ constructor(ws) {
45
+ this.ws = ws;
46
+ this.id = 0;
47
+ this.callbacks = new Map();
48
+ this.sessions = new Map();
49
+ this.eventHandlers = new Map();
50
+
51
+ ws.on("message", (data) => {
52
+ const msg = JSON.parse(data.toString());
53
+ if (msg.id && this.callbacks.has(msg.id)) {
54
+ const { resolve, reject } = this.callbacks.get(msg.id);
55
+ this.callbacks.delete(msg.id);
56
+ if (msg.error) {
57
+ reject(new Error(msg.error.message));
58
+ } else {
59
+ resolve(msg.result);
60
+ }
61
+ return;
62
+ }
63
+
64
+ if (msg.method) {
65
+ this.emit(msg.method, msg.params || {}, msg.sessionId || null);
66
+ }
67
+ });
68
+ }
69
+
70
+ on(method, handler) {
71
+ if (!this.eventHandlers.has(method)) {
72
+ this.eventHandlers.set(method, new Set());
73
+ }
74
+ this.eventHandlers.get(method).add(handler);
75
+ return () => this.off(method, handler);
76
+ }
77
+
78
+ off(method, handler) {
79
+ const handlers = this.eventHandlers.get(method);
80
+ if (!handlers) return;
81
+ handlers.delete(handler);
82
+ if (handlers.size === 0) {
83
+ this.eventHandlers.delete(method);
84
+ }
85
+ }
86
+
87
+ emit(method, params, sessionId) {
88
+ const handlers = this.eventHandlers.get(method);
89
+ if (!handlers || handlers.size === 0) return;
90
+ for (const handler of handlers) {
91
+ try {
92
+ handler(params, sessionId);
93
+ } catch {
94
+ // Ignore handler errors to keep CDP session alive.
95
+ }
96
+ }
97
+ }
98
+
99
+ send(method, params = {}, sessionId = null, timeout = 10000) {
100
+ return new Promise((resolve, reject) => {
101
+ const msgId = ++this.id;
102
+ const msg = { id: msgId, method, params };
103
+ if (sessionId) msg.sessionId = sessionId;
104
+
105
+ const timeoutId = setTimeout(() => {
106
+ this.callbacks.delete(msgId);
107
+ reject(new Error(`CDP timeout: ${method}`));
108
+ }, timeout);
109
+
110
+ this.callbacks.set(msgId, {
111
+ resolve: (result) => {
112
+ clearTimeout(timeoutId);
113
+ resolve(result);
114
+ },
115
+ reject: (err) => {
116
+ clearTimeout(timeoutId);
117
+ reject(err);
118
+ },
119
+ });
120
+
121
+ this.ws.send(JSON.stringify(msg));
122
+ });
123
+ }
124
+
125
+ async getPages() {
126
+ const { targetInfos } = await this.send("Target.getTargets");
127
+ return targetInfos.filter((t) => t.type === "page");
128
+ }
129
+
130
+ async attachToPage(targetId) {
131
+ const { sessionId } = await this.send("Target.attachToTarget", {
132
+ targetId,
133
+ flatten: true,
134
+ });
135
+ return sessionId;
136
+ }
137
+
138
+ async evaluate(sessionId, expression, timeout = 30000) {
139
+ const result = await this.send(
140
+ "Runtime.evaluate",
141
+ {
142
+ expression,
143
+ returnByValue: true,
144
+ awaitPromise: true,
145
+ },
146
+ sessionId,
147
+ timeout
148
+ );
149
+
150
+ if (result.exceptionDetails) {
151
+ throw new Error(
152
+ result.exceptionDetails.exception?.description ||
153
+ result.exceptionDetails.text
154
+ );
155
+ }
156
+ return result.result?.value;
157
+ }
158
+
159
+ async screenshot(sessionId, timeout = 10000) {
160
+ const { data } = await this.send(
161
+ "Page.captureScreenshot",
162
+ { format: "png" },
163
+ sessionId,
164
+ timeout
165
+ );
166
+ return Buffer.from(data, "base64");
167
+ }
168
+
169
+ async navigate(sessionId, url, timeout = 30000) {
170
+ await this.send("Page.navigate", { url }, sessionId, timeout);
171
+ }
172
+
173
+ async getFrameTree(sessionId) {
174
+ const { frameTree } = await this.send("Page.getFrameTree", {}, sessionId);
175
+ return frameTree;
176
+ }
177
+
178
+ async evaluateInFrame(sessionId, frameId, expression, timeout = 30000) {
179
+ // Create isolated world for the frame
180
+ const { executionContextId } = await this.send(
181
+ "Page.createIsolatedWorld",
182
+ { frameId, worldName: "cdp-eval" },
183
+ sessionId
184
+ );
185
+
186
+ const result = await this.send(
187
+ "Runtime.evaluate",
188
+ {
189
+ expression,
190
+ contextId: executionContextId,
191
+ returnByValue: true,
192
+ awaitPromise: true,
193
+ },
194
+ sessionId,
195
+ timeout
196
+ );
197
+
198
+ if (result.exceptionDetails) {
199
+ throw new Error(
200
+ result.exceptionDetails.exception?.description ||
201
+ result.exceptionDetails.text
202
+ );
203
+ }
204
+ return result.result?.value;
205
+ }
206
+
207
+ close() {
208
+ this.ws.close();
209
+ }
210
+ }
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cookie Consent Dismissal Helper
5
+ *
6
+ * Automatically accepts cookie consent dialogs on EU websites.
7
+ * Supports common CMPs: OneTrust, Cookiebot, Didomi, Quantcast, Google, BBC, Amazon, etc.
8
+ *
9
+ * Usage:
10
+ * ./dismiss-cookies.js # Accept cookies
11
+ * ./dismiss-cookies.js --reject # Reject cookies (where possible)
12
+ */
13
+
14
+ import { connect } from "./cdp.js";
15
+
16
+ const DEBUG = process.env.DEBUG === "1";
17
+ const log = DEBUG ? (...args) => console.error("[debug]", ...args) : () => {};
18
+
19
+ const reject = process.argv.includes("--reject");
20
+ const mode = reject ? "reject" : "accept";
21
+
22
+ const COOKIE_DISMISS_SCRIPT = `(acceptCookies) => {
23
+ const clicked = [];
24
+
25
+ const isVisible = (el) => {
26
+ if (!el) return false;
27
+ const style = getComputedStyle(el);
28
+ return style.display !== 'none' &&
29
+ style.visibility !== 'hidden' &&
30
+ style.opacity !== '0' &&
31
+ (el.offsetParent !== null || style.position === 'fixed' || style.position === 'sticky');
32
+ };
33
+
34
+ const tryClick = (selector, description) => {
35
+ const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
36
+ if (isVisible(el)) {
37
+ el.click();
38
+ clicked.push(description || selector);
39
+ return true;
40
+ }
41
+ return false;
42
+ };
43
+
44
+ const findButtonByText = (patterns, container = document) => {
45
+ const buttons = Array.from(container.querySelectorAll('button, [role="button"], a.button, input[type="submit"], input[type="button"]'));
46
+ // Sort patterns by length descending to match more specific patterns first
47
+ const sortedPatterns = [...patterns].sort((a, b) => b.length - a.length);
48
+
49
+ // Check patterns in order of specificity (longest first)
50
+ for (const pattern of sortedPatterns) {
51
+ for (const btn of buttons) {
52
+ const text = (btn.textContent || btn.value || '').trim().toLowerCase();
53
+ if (text.length > 100) continue; // Skip buttons with very long text
54
+ if (!isVisible(btn)) continue; // Skip hidden buttons
55
+ if (typeof pattern === 'string' ? text.includes(pattern) : pattern.test(text)) {
56
+ return btn;
57
+ }
58
+ }
59
+ }
60
+ return null;
61
+ };
62
+
63
+ const acceptPatterns = [
64
+ 'accept all', 'accept cookies', 'allow all', 'allow cookies',
65
+ 'i agree', 'i accept', 'yes, i agree', 'agree and continue',
66
+ 'alle akzeptieren', 'akzeptieren', 'alle zulassen', 'zustimmen',
67
+ 'annehmen', 'einverstanden',
68
+ 'accepter tout', 'tout accepter', "j'accepte", 'accepter et continuer', 'accepter',
69
+ 'accetta tutti', 'accetta', 'accetto',
70
+ 'aceptar todo', 'aceptar', 'acepto',
71
+ 'aceitar tudo', 'aceitar',
72
+ 'continue', 'agree',
73
+ ];
74
+
75
+ const rejectPatterns = [
76
+ 'reject all', 'decline all', 'deny all', 'refuse all',
77
+ 'i do not agree', 'i disagree', 'no thanks',
78
+ 'alle ablehnen', 'ablehnen', 'nicht zustimmen',
79
+ 'refuser tout', 'tout refuser', 'refuser',
80
+ 'rifiuta tutti', 'rifiuta',
81
+ 'rechazar todo', 'rechazar',
82
+ 'rejeitar tudo', 'rejeitar',
83
+ 'only necessary', 'necessary only', 'nur notwendige',
84
+ 'essential only', 'nur essentielle',
85
+ ];
86
+
87
+ const patterns = acceptCookies ? acceptPatterns : rejectPatterns;
88
+
89
+ // OneTrust
90
+ if (document.querySelector('#onetrust-banner-sdk')) {
91
+ const selector = acceptCookies ? '#onetrust-accept-btn-handler' : '#onetrust-reject-all-handler';
92
+ if (tryClick(selector, 'OneTrust')) return clicked;
93
+ }
94
+
95
+ // Google
96
+ if (document.querySelector('[data-consent-dialog]') || document.querySelector('form[action*="consent.google"]') || document.querySelector('#CXQnmb')) {
97
+ const selector = acceptCookies ? '#L2AGLb' : '#W0wltc';
98
+ if (tryClick(selector, 'Google Consent')) return clicked;
99
+ }
100
+
101
+ // YouTube (Google-owned, custom consent element)
102
+ if (document.querySelector('ytd-consent-bump-v2-lightbox')) {
103
+ const btn = Array.from(document.querySelectorAll('ytd-consent-bump-v2-lightbox button'))
104
+ .find(b => acceptCookies
105
+ ? b.textContent.includes('Accept all') || b.ariaLabel?.includes('Accept')
106
+ : b.textContent.includes('Reject all') || b.ariaLabel?.includes('Reject'));
107
+ if (btn) {
108
+ btn.click();
109
+ clicked.push('YouTube');
110
+ return clicked;
111
+ }
112
+ }
113
+
114
+ // Cookiebot
115
+ if (document.querySelector('#CybotCookiebotDialog')) {
116
+ const selector = acceptCookies
117
+ ? '#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll, #CybotCookiebotDialogBodyButtonAccept'
118
+ : '#CybotCookiebotDialogBodyButtonDecline, #CybotCookiebotDialogBodyLevelButtonLevelOptinDeclineAll';
119
+ if (tryClick(selector, 'Cookiebot')) return clicked;
120
+ }
121
+
122
+ // Didomi
123
+ if (document.querySelector('#didomi-host') || window.Didomi) {
124
+ const selector = acceptCookies ? '#didomi-notice-agree-button' : '#didomi-notice-disagree-button, [data-testid="disagree-button"]';
125
+ if (tryClick(selector, 'Didomi')) return clicked;
126
+ }
127
+
128
+ // Quantcast
129
+ if (document.querySelector('.qc-cmp2-container')) {
130
+ const selector = acceptCookies
131
+ ? '.qc-cmp2-summary-buttons button[mode="primary"], .qc-cmp2-button[data-testid="accept-all"]'
132
+ : '.qc-cmp2-summary-buttons button[mode="secondary"], .qc-cmp2-button[data-testid="reject-all"]';
133
+ if (tryClick(selector, 'Quantcast')) return clicked;
134
+ }
135
+
136
+ // Usercentrics (shadow DOM)
137
+ const ucRoot = document.querySelector('#usercentrics-root');
138
+ if (ucRoot && ucRoot.shadowRoot) {
139
+ const shadow = ucRoot.shadowRoot;
140
+ const btn = acceptCookies
141
+ ? shadow.querySelector('[data-testid="uc-accept-all-button"]')
142
+ : shadow.querySelector('[data-testid="uc-deny-all-button"]');
143
+ if (btn) { btn.click(); clicked.push('Usercentrics'); return clicked; }
144
+ }
145
+
146
+ // TrustArc
147
+ if (document.querySelector('#truste-consent-track') || document.querySelector('.trustarc-banner')) {
148
+ const selector = acceptCookies ? '#truste-consent-button, .trustarc-agree-btn' : '.trustarc-decline-btn';
149
+ if (tryClick(selector, 'TrustArc')) return clicked;
150
+ }
151
+
152
+ // Klaro
153
+ if (document.querySelector('.klaro')) {
154
+ const selector = acceptCookies ? '.klaro .cm-btn-accept-all, .klaro .cm-btn-success' : '.klaro .cm-btn-decline';
155
+ if (tryClick(selector, 'Klaro')) return clicked;
156
+ }
157
+
158
+ // BBC
159
+ if (document.querySelector('#bbccookies, .bbccookies-banner')) {
160
+ if (acceptCookies && tryClick('#bbccookies-continue-button', 'BBC')) return clicked;
161
+ }
162
+
163
+ // Amazon
164
+ if (document.querySelector('#sp-cc') || document.querySelector('#sp-cc-accept')) {
165
+ const selector = acceptCookies ? '#sp-cc-accept' : '#sp-cc-rejectall-link, #sp-cc-decline';
166
+ if (tryClick(selector, 'Amazon')) return clicked;
167
+ }
168
+
169
+ // CookieYes
170
+ if (document.querySelector('#cookie-law-info-bar') || document.querySelector('.cky-consent-container')) {
171
+ const selector = acceptCookies ? '#cookie_action_close_header, .cky-btn-accept' : '.cky-btn-reject';
172
+ if (tryClick(selector, 'CookieYes')) return clicked;
173
+ }
174
+
175
+ // Generic containers
176
+ const consentContainers = [
177
+ '[class*="cookie-banner"]', '[class*="cookie-consent"]', '[class*="cookie-notice"]',
178
+ '[class*="cookieBanner"]', '[class*="cookieConsent"]', '[class*="cookieNotice"]',
179
+ '[id*="cookie-banner"]', '[id*="cookie-consent"]', '[id*="cookie-notice"]',
180
+ '[class*="consent-banner"]', '[class*="consent-modal"]', '[class*="consent-dialog"]',
181
+ '[class*="gdpr"]', '[id*="gdpr"]', '[class*="privacy-banner"]', '[class*="privacy-notice"]',
182
+ '[role="dialog"][aria-label*="cookie" i]', '[role="dialog"][aria-label*="consent" i]',
183
+ ];
184
+
185
+ for (const containerSel of consentContainers) {
186
+ const containers = document.querySelectorAll(containerSel);
187
+ for (const container of containers) {
188
+ if (!isVisible(container)) continue;
189
+ // Skip html/body elements that might match
190
+ if (container.tagName === 'HTML' || container.tagName === 'BODY') continue;
191
+ const btn = findButtonByText(patterns, container);
192
+ if (btn) { btn.click(); clicked.push('Generic (' + containerSel + ')'); return clicked; }
193
+ }
194
+ }
195
+
196
+ // Last resort: find button near cookie-related text content
197
+ // Look for visible containers that mention "cookie" and have accept/reject buttons
198
+ // Include custom elements (Reddit uses rpl-modal-card, etc.)
199
+ const allContainers = document.querySelectorAll('div, section, aside, [class*="modal"], [class*="dialog"], [role="dialog"]');
200
+ for (const container of allContainers) {
201
+ if (!isVisible(container)) continue;
202
+ const text = container.textContent?.toLowerCase() || '';
203
+ // Must mention cookies and be reasonably sized (not the whole page)
204
+ if (text.includes('cookie') && text.length > 100 && text.length < 3000) {
205
+ const btn = findButtonByText(patterns, container);
206
+ if (btn && isVisible(btn)) {
207
+ btn.click();
208
+ clicked.push('Generic (text-based)');
209
+ return clicked;
210
+ }
211
+ }
212
+ }
213
+
214
+ // Final fallback: look for any visible button with exact accept/reject text
215
+ // that appears alongside cookie-related content on the page
216
+ if (document.body.textContent?.toLowerCase().includes('cookie')) {
217
+ const exactPatterns = acceptCookies
218
+ ? ['accept all', 'accept cookies', 'allow all', 'i agree', 'alle akzeptieren']
219
+ : ['reject all', 'decline all', 'reject optional', 'alle ablehnen'];
220
+ const singleWordPatterns = acceptCookies ? ['accept', 'agree'] : ['reject', 'decline'];
221
+ const buttons = document.querySelectorAll('button, [role="button"]');
222
+ for (const btn of buttons) {
223
+ if (!isVisible(btn)) continue;
224
+ const text = (btn.textContent || '').trim().toLowerCase();
225
+ if (exactPatterns.some(p => text.includes(p))) {
226
+ btn.click();
227
+ clicked.push('Generic (exact match)');
228
+ return clicked;
229
+ }
230
+ }
231
+ // Try single-word matches as last resort
232
+ for (const btn of buttons) {
233
+ if (!isVisible(btn)) continue;
234
+ const text = (btn.textContent || '').trim().toLowerCase();
235
+ if (singleWordPatterns.some(p => text === p)) {
236
+ btn.click();
237
+ clicked.push('Generic (single word)');
238
+ return clicked;
239
+ }
240
+ }
241
+ }
242
+
243
+ return clicked;
244
+ }`;
245
+
246
+ const IFRAME_DISMISS_SCRIPT = `(acceptCookies) => {
247
+ const clicked = [];
248
+
249
+ const isVisible = (el) => {
250
+ if (!el) return false;
251
+ const style = getComputedStyle(el);
252
+ return style.display !== 'none' &&
253
+ style.visibility !== 'hidden' &&
254
+ style.opacity !== '0' &&
255
+ (el.offsetParent !== null || style.position === 'fixed' || style.position === 'sticky');
256
+ };
257
+
258
+ const rejectIndicators = ['do not', "don't", 'nicht', 'no ', 'refuse', 'reject', 'decline', 'deny', 'disagree', 'ablehnen', 'refuser', 'rifiuta', 'rechazar', 'manage', 'settings', 'options', 'customize'];
259
+ const acceptIndicators = ['accept', 'agree', 'allow', 'yes', 'ok', 'got it', 'continue', 'akzeptieren', 'zustimmen', 'accepter', 'accetta', 'aceptar'];
260
+
261
+ const isRejectButton = (text) => rejectIndicators.some(p => text.includes(p));
262
+ const isAcceptButton = (text) => acceptIndicators.some(p => text.includes(p)) && !isRejectButton(text);
263
+
264
+ const buttons = document.querySelectorAll('button, [role="button"]');
265
+ for (const btn of buttons) {
266
+ const text = (btn.textContent || '').trim().toLowerCase();
267
+ if (!isVisible(btn)) continue;
268
+ const shouldClick = acceptCookies ? isAcceptButton(text) : isRejectButton(text);
269
+ if (shouldClick) { btn.click(); clicked.push('iframe: ' + text.slice(0, 30)); return clicked; }
270
+ }
271
+
272
+ const spBtn = acceptCookies
273
+ ? document.querySelector('[title="Accept All"], [title="Accept"], [aria-label*="Accept"]')
274
+ : document.querySelector('[title="Reject All"], [title="Reject"], [aria-label*="Reject"]');
275
+ if (spBtn) { spBtn.click(); clicked.push('Sourcepoint iframe'); return clicked; }
276
+
277
+ return clicked;
278
+ }`;
279
+
280
+ // Recursively collect all frames
281
+ function collectFrames(frameTree, frames = []) {
282
+ frames.push({ id: frameTree.frame.id, url: frameTree.frame.url });
283
+ if (frameTree.childFrames) {
284
+ for (const child of frameTree.childFrames) {
285
+ collectFrames(child, frames);
286
+ }
287
+ }
288
+ return frames;
289
+ }
290
+
291
+ // Global timeout
292
+ const globalTimeout = setTimeout(() => {
293
+ console.error("✗ Global timeout exceeded (30s)");
294
+ process.exit(1);
295
+ }, 30000);
296
+
297
+ try {
298
+ log("connecting...");
299
+ const cdp = await connect(5000);
300
+
301
+ log("getting pages...");
302
+ const pages = await cdp.getPages();
303
+ const page = pages.at(-1);
304
+
305
+ if (!page) {
306
+ console.error("✗ No active tab found");
307
+ process.exit(1);
308
+ }
309
+
310
+ log("attaching to page...");
311
+ const sessionId = await cdp.attachToPage(page.targetId);
312
+
313
+ // Wait a bit for consent dialogs to appear
314
+ await new Promise((r) => setTimeout(r, 500));
315
+
316
+ log("trying main page...");
317
+ let result = await cdp.evaluate(sessionId, `(${COOKIE_DISMISS_SCRIPT})(${!reject})`);
318
+
319
+ // If nothing found, try iframes
320
+ if (result.length === 0) {
321
+ log("trying iframes...");
322
+ try {
323
+ const frameTree = await cdp.getFrameTree(sessionId);
324
+ const frames = collectFrames(frameTree);
325
+
326
+ for (const frame of frames) {
327
+ if (frame.url === "about:blank" || frame.url.startsWith("javascript:")) continue;
328
+ if (
329
+ frame.url.includes("sp_message") ||
330
+ frame.url.includes("consent") ||
331
+ frame.url.includes("privacy") ||
332
+ frame.url.includes("cmp") ||
333
+ frame.url.includes("sourcepoint") ||
334
+ frame.url.includes("cookie") ||
335
+ frame.url.includes("privacy-mgmt")
336
+ ) {
337
+ log("trying frame:", frame.url.slice(0, 60));
338
+ try {
339
+ const frameResult = await cdp.evaluateInFrame(
340
+ sessionId,
341
+ frame.id,
342
+ `(${IFRAME_DISMISS_SCRIPT})(${!reject})`
343
+ );
344
+ if (frameResult.length > 0) {
345
+ result = frameResult;
346
+ break;
347
+ }
348
+ } catch (e) {
349
+ log("frame error:", e.message);
350
+ }
351
+ }
352
+ }
353
+ } catch (e) {
354
+ log("getFrameTree error:", e.message);
355
+ }
356
+ }
357
+
358
+ if (result.length > 0) {
359
+ console.log(`✓ Dismissed cookie dialog (${mode}): ${result.join(", ")}`);
360
+ } else {
361
+ console.log(`○ No cookie dialog found to ${mode}`);
362
+ }
363
+
364
+ log("closing...");
365
+ cdp.close();
366
+ log("done");
367
+ } catch (e) {
368
+ console.error("✗", e.message);
369
+ process.exit(1);
370
+ } finally {
371
+ clearTimeout(globalTimeout);
372
+ setTimeout(() => process.exit(0), 100);
373
+ }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { connect } from "./cdp.js";
4
+
5
+ const DEBUG = process.env.DEBUG === "1";
6
+ const log = DEBUG ? (...args) => console.error("[debug]", ...args) : () => {};
7
+
8
+ const code = process.argv.slice(2).join(" ");
9
+ if (!code) {
10
+ console.log("Usage: eval.js 'code'");
11
+ console.log("\nExamples:");
12
+ console.log(' eval.js "document.title"');
13
+ console.log(" eval.js \"document.querySelectorAll('a').length\"");
14
+ process.exit(1);
15
+ }
16
+
17
+ // Global timeout
18
+ const globalTimeout = setTimeout(() => {
19
+ console.error("✗ Global timeout exceeded (45s)");
20
+ process.exit(1);
21
+ }, 45000);
22
+
23
+ try {
24
+ log("connecting...");
25
+ const cdp = await connect(5000);
26
+
27
+ log("getting pages...");
28
+ const pages = await cdp.getPages();
29
+ const page = pages.at(-1);
30
+
31
+ if (!page) {
32
+ console.error("✗ No active tab found");
33
+ process.exit(1);
34
+ }
35
+
36
+ log("attaching to page...");
37
+ const sessionId = await cdp.attachToPage(page.targetId);
38
+
39
+ log("evaluating...");
40
+ const expression = `(async () => { return (${code}); })()`;
41
+ const result = await cdp.evaluate(sessionId, expression);
42
+
43
+ log("formatting result...");
44
+ if (Array.isArray(result)) {
45
+ for (let i = 0; i < result.length; i++) {
46
+ if (i > 0) console.log("");
47
+ for (const [key, value] of Object.entries(result[i])) {
48
+ console.log(`${key}: ${value}`);
49
+ }
50
+ }
51
+ } else if (typeof result === "object" && result !== null) {
52
+ for (const [key, value] of Object.entries(result)) {
53
+ console.log(`${key}: ${value}`);
54
+ }
55
+ } else {
56
+ console.log(result);
57
+ }
58
+
59
+ log("closing...");
60
+ cdp.close();
61
+ log("done");
62
+ } catch (e) {
63
+ console.error("✗", e.message);
64
+ process.exit(1);
65
+ } finally {
66
+ clearTimeout(globalTimeout);
67
+ setTimeout(() => process.exit(0), 100);
68
+ }