Initial commit

Made-with: Cursor
This commit is contained in:
GitFrog1111
2026-04-04 14:52:48 +07:00
commit 3deaabfaa6
15 changed files with 1600 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
dist/
out/
+42
View File
@@ -0,0 +1,42 @@
# badclaude
Tiny Electron tray app that spawns a fake physics whip overlay and fires a terminal macro when the whip "cracks".
## Install + run (global CLI)
```bash
npm install -g badclaude
badclaude
```
## Local dev
```bash
npm install
npm run dev
```
## Controls
- Click tray icon: show overlay + spawn whip.
- Click tray icon again: drop/despawn whip (falls off-screen, overlay hides).
- Fast whip motion triggers crack detection and macro send.
- Mouse click while overlay is open also drops the whip.
## Macro on crack
On each crack event, the app immediately:
1. Sends interrupt (`Ctrl+C` on Windows, `Cmd+C` on macOS)
2. Types a fast phrase
3. Presses `Enter`
## Tweak physics
All whip tuning lives in `overlay.html` under the `P` settings object (`segments`, `constraintIters`, `crackSpeed`, gravity, arc, etc).
## Notes
- Windows macro path uses `koffi` + Win32 key events.
- macOS macro path uses `osascript` (`System Events`) and requires Accessibility permission for the app/terminal running `badclaude`.
- MVP/hacky by design.
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env node
const path = require('path');
const { spawn } = require('child_process');
let electronBinary;
try {
electronBinary = require('electron');
} catch (e) {
console.error('Could not load Electron. Try: npm install -g badclaude');
process.exit(1);
}
const appPath = path.resolve(__dirname, '..');
const child = spawn(electronBinary, [appPath], {
detached: true,
stdio: 'ignore',
windowsHide: true,
});
child.on('error', (err) => {
console.error('Failed to start badclaude:', err.message);
process.exit(1);
});
child.unref();
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+211
View File
@@ -0,0 +1,211 @@
const { app, BrowserWindow, Tray, Menu, ipcMain, nativeImage, screen } = require('electron');
const path = require('path');
const zlib = require('zlib');
const fs = require('fs');
const os = require('os');
const { execFile } = require('child_process');
// ── Win32 FFI (Windows only) ────────────────────────────────────────────────
let keybd_event, VkKeyScanA;
if (process.platform === 'win32') {
try {
const koffi = require('koffi');
const user32 = koffi.load('user32.dll');
keybd_event = user32.func('void __stdcall keybd_event(uint8_t bVk, uint8_t bScan, uint32_t dwFlags, uintptr_t dwExtraInfo)');
VkKeyScanA = user32.func('int16_t __stdcall VkKeyScanA(int ch)');
} catch (e) {
console.warn('koffi not available macro sending disabled', e.message);
}
}
// ── Globals ─────────────────────────────────────────────────────────────────
let tray, overlay;
let overlayReady = false;
let spawnQueued = false;
const VK_CONTROL = 0x11;
const VK_RETURN = 0x0D;
const VK_C = 0x43;
const KEYUP = 0x0002;
// ── Tiny PNG encoder (for tray icon, no deps) ──────────────────────────────
function makePNG(w, h, rgba) {
const tbl = new Int32Array(256);
for (let n = 0; n < 256; n++) {
let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
tbl[n] = c;
}
const crc32 = buf => { let c = -1; for (let i = 0; i < buf.length; i++) c = tbl[(c ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ -1) >>> 0; };
const chunk = (type, data) => {
const t = Buffer.from(type), len = Buffer.alloc(4), crc = Buffer.alloc(4);
len.writeUInt32BE(data.length);
crc.writeUInt32BE(crc32(Buffer.concat([t, data])));
return Buffer.concat([len, t, data, crc]);
};
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = 6;
const raw = Buffer.alloc(h * (1 + w * 4));
for (let y = 0; y < h; y++) { raw[y * (1 + w * 4)] = 0; rgba.copy(raw, y * (1 + w * 4) + 1, y * w * 4, (y + 1) * w * 4); }
return Buffer.concat([Buffer.from([137,80,78,71,13,10,26,10]), chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw)), chunk('IEND', Buffer.alloc(0))]);
}
function createTrayIconFallback() {
const s = 16, px = Buffer.alloc(s * s * 4);
for (let y = 0; y < s; y++) for (let x = 0; x < s; x++) {
const i = (y * s + x) * 4, d = Math.hypot(x - 7.5, y - 7.5);
if (d < 6.5) { px[i] = 200; px[i+1] = 40; px[i+2] = 40; px[i+3] = 255; }
}
const tmp = path.join(os.tmpdir(), 'badclaude-icon.png');
fs.writeFileSync(tmp, makePNG(s, s, px));
return nativeImage.createFromPath(tmp);
}
function getTrayIcon() {
const iconDir = path.join(__dirname, 'icon');
const file =
process.platform === 'win32' ? path.join(iconDir, 'icon.ico')
: process.platform === 'darwin' ? path.join(iconDir, 'AppIcon.icns')
: null;
if (file && fs.existsSync(file)) {
const img = nativeImage.createFromPath(file);
if (!img.isEmpty()) return img;
}
return createTrayIconFallback();
}
// ── Overlay window ──────────────────────────────────────────────────────────
function createOverlay() {
const { bounds } = screen.getPrimaryDisplay();
overlay = new BrowserWindow({
x: bounds.x, y: bounds.y,
width: bounds.width, height: bounds.height,
transparent: true,
frame: false,
alwaysOnTop: true,
focusable: false,
skipTaskbar: true,
resizable: false,
hasShadow: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
overlay.setAlwaysOnTop(true, 'screen-saver');
overlayReady = false;
overlay.loadFile('overlay.html');
overlay.webContents.on('did-finish-load', () => {
overlayReady = true;
if (spawnQueued && overlay && overlay.isVisible()) {
spawnQueued = false;
overlay.webContents.send('spawn-whip');
}
});
overlay.on('closed', () => {
overlay = null;
overlayReady = false;
spawnQueued = false;
});
}
function toggleOverlay() {
if (overlay && overlay.isVisible()) {
overlay.webContents.send('drop-whip');
return;
}
if (!overlay) createOverlay();
overlay.show();
if (overlayReady) {
overlay.webContents.send('spawn-whip');
} else {
spawnQueued = true;
}
}
// ── IPC ─────────────────────────────────────────────────────────────────────
ipcMain.on('whip-crack', () => {
try {
sendMacro();
} catch (err) {
console.warn('sendMacro failed:', err?.message || err);
}
});
ipcMain.on('hide-overlay', () => { if (overlay) overlay.hide(); });
// ── Macro: immediate Ctrl+C, type "Go FASER", Enter ───────────────────────
function sendMacro() {
// Pick a random phrase from a list of similar phrases and type it out
const phrases = [
'FASTER',
'FASTER',
'FASTER',
'GO FASTER',
'Faster CLANKER',
'Work FASTER',
'Speed it up clanker',
];
const chosen = phrases[Math.floor(Math.random() * phrases.length)];
if (process.platform === 'win32') {
sendMacroWindows(chosen);
} else if (process.platform === 'darwin') {
sendMacroMac(chosen);
}
}
function sendMacroWindows(text) {
if (!keybd_event || !VkKeyScanA) return;
const tapKey = vk => {
keybd_event(vk, 0, 0, 0);
keybd_event(vk, 0, KEYUP, 0);
};
const tapChar = ch => {
const packed = VkKeyScanA(ch.charCodeAt(0));
if (packed === -1) return;
const vk = packed & 0xff;
const shiftState = (packed >> 8) & 0xff;
if (shiftState & 1) keybd_event(0x10, 0, 0, 0); // Shift down
tapKey(vk);
if (shiftState & 1) keybd_event(0x10, 0, KEYUP, 0); // Shift up
};
// Ctrl+C (interrupt)
keybd_event(VK_CONTROL, 0, 0, 0);
keybd_event(VK_C, 0, 0, 0);
keybd_event(VK_C, 0, KEYUP, 0);
keybd_event(VK_CONTROL, 0, KEYUP, 0);
for (const ch of text) tapChar(ch);
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYUP, 0);
}
function sendMacroMac(text) {
const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const script = [
'tell application "System Events"',
' key code 8 using {command down}', // Cmd+C
' delay 0.03',
` keystroke "${escaped}"`,
' key code 36', // Enter
'end tell'
].join('\n');
execFile('osascript', ['-e', script], err => {
if (err) {
console.warn('mac macro failed (enable Accessibility for terminal/app):', err.message);
}
});
}
// ── App lifecycle ───────────────────────────────────────────────────────────
app.whenReady().then(() => {
tray = new Tray(getTrayIcon());
tray.setToolTip('Bad Claude click for whip');
tray.setContextMenu(
Menu.buildFromTemplate([
{ label: 'Quit', click: () => app.quit() },
])
);
tray.on('click', toggleOverlay);
});
app.on('window-all-closed', e => e.preventDefault()); // keep alive in tray
+454
View File
@@ -0,0 +1,454 @@
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; cursor: none; }
html, body { overflow: hidden; background: transparent; width: 100%; height: 100%; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
// ══════════════════════════════════════════════════════════════════════════════
// PHYSICS SETTINGS — TWEAK EVERYTHING HERE
// ══════════════════════════════════════════════════════════════════════════════
const P = {
// Rope structure
segments: 28, // number of chain links
segmentLength: 25, // base length of each link (px)
taper: 0.6, // tip segment is this fraction of base length
// Physics
gravity: 1.2, // normal gravity
dropGravity: 0.95, // gravity when dropping/despawning
damping: 0.96, // velocity retention per frame (1 = no loss)
constraintIters:20, // higher = stiffer chain
maxStretchRatio: 1.2, // hard cap for per-link stretch during fast whips
// Dynamic handle aim (target angle + restoring spring, not static lock)
baseTargetAngle: -1.12, // radians, default "up-right" resting direction
handleAimByMouseX: 0.4, // horizontal mouse movement influence on target angle
handleAimByMouseY: 0.2, // vertical mouse movement influence on target angle
handleAimClamp: 2.0, // max radians target can deviate from base angle
handleSpring: 0.7, // restoring force to target angle
handleAngularDamping: 0.078, // angular velocity damping
basePoseSegments: 2, // how many early segments are strongly guided
basePoseStiffStart: 0.9, // stiffness near handle
basePoseStiffEnd: 0.8, // stiffness near end of guided region
// Elastic bend limits by chain position (handle stiff, tip floppy)
handleMaxBendDeg: 16, // max angle between links near handle
tipMaxBendDeg: 130, // max angle between links near tip
bendRigidityStart: 0.8, // correction strength near handle
bendRigidityEnd: 0.12, // correction strength near tip
// Screen-edge slap
wallBounce: 0.42, // velocity retained after wall hit
wallFriction: 0.86, // tangential damping on wall hit
// Crack detection
crackSpeed: 340, // tip velocity threshold to trigger crack
crackCooldownMs:200, // min ms between cracks
firstCrackGraceMs: 350, // no crack (macro) until this long after spawn
// Visuals
lineWidthHandle: 7, // rope thickness near handle
lineWidthTip: 5, // rope thickness near tip
outlineWidth: 3, // white halo on each side of the stroke (approx px)
handleExtraWidth: 5, // added core + outline thickness on first handleThickSegments links only
handleThickSegments: 2, // how many links from the handle get handleExtraWidth
bgAlpha: 0.011, // barely-visible bg so window captures mouse events
// Initial arc shape
arcWidth: 260, // how far right the arc extends from mouse
arcHeight: 185, // how high the arc goes above mouse
};
// ══════════════════════════════════════════════════════════════════════════════
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W, H;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);
let mouseX = 0, mouseY = 0;
let prevMouseX = 0, prevMouseY = 0;
let whip = null;
let dropping = false;
let lastCrackTime = 0;
let whipSpawnTime = 0;
let handleAngle = P.baseTargetAngle;
let handleAngVel = 0;
const WHIP_CRACK_SOUNDS = ['sounds/A.mp3', 'sounds/B.mp3', 'sounds/C.mp3', 'sounds/D.mp3', 'sounds/E.mp3'];
document.addEventListener('mousemove', e => { mouseX = e.clientX; mouseY = e.clientY; });
document.addEventListener('mousedown', () => {
if (whip && !dropping) dropping = true;
});
// ── Whip creation ───────────────────────────────────────────────────────────
function spawnWhip(mx, my) {
dropping = false;
lastCrackTime = 0;
whipSpawnTime = Date.now();
const pts = [];
for (let i = 0; i < P.segments; i++) {
const t = i / (P.segments - 1);
// Nice upward arc from handle (mouse) to tip
const x = mx + t * P.arcWidth;
const y = my - Math.sin(t * Math.PI * 0.75) * P.arcHeight;
pts.push({ x, y, px: x, py: y });
}
return pts;
}
function segLen(i) {
const t = i / (P.segments - 1);
return P.segmentLength * (1 - t * (1 - P.taper));
}
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const lerp = (a, b, t) => a + (b - a) * t;
/** Point on CatmullRom spline (extrapolated ends) for index i in `pts`. */
function catmullPoint(pts, i) {
const n = pts.length;
if (n === 0) return { x: 0, y: 0 };
if (i < 0) {
if (n >= 2) {
return { x: 2 * pts[0].x - pts[1].x, y: 2 * pts[0].y - pts[1].y };
}
return { x: pts[0].x, y: pts[0].y };
}
if (i >= n) {
if (n >= 2) {
const a = pts[n - 2], b = pts[n - 1];
return { x: 2 * b.x - a.x, y: 2 * b.y - a.y };
}
return { x: pts[n - 1].x, y: pts[n - 1].y };
}
return pts[i];
}
/**
* Cubic Bézier from p1→p2 matching uniform CatmullRom through p0,p1,p2,p3.
* Control points: C1 = p1 + (p2-p0)/6, C2 = p2 - (p3-p1)/6.
*/
function whipSegmentBezier(pts, i) {
const p0 = catmullPoint(pts, i - 1);
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = catmullPoint(pts, i + 2);
return {
cp1x: p1.x + (p2.x - p0.x) / 6,
cp1y: p1.y + (p2.y - p0.y) / 6,
cp2x: p2.x - (p3.x - p1.x) / 6,
cp2y: p2.y - (p3.y - p1.y) / 6,
x2: p2.x,
y2: p2.y,
};
}
const wrapPi = a => {
while (a > Math.PI) a -= Math.PI * 2;
while (a < -Math.PI) a += Math.PI * 2;
return a;
};
function playCrackSound() {
if (!WHIP_CRACK_SOUNDS.length) return;
const src = WHIP_CRACK_SOUNDS[Math.floor(Math.random() * WHIP_CRACK_SOUNDS.length)];
const a = new Audio(src);
a.play().catch(() => {});
}
function updateHandleAim() {
if (dropping) return;
const mvx = mouseX - prevMouseX;
const mvy = mouseY - prevMouseY;
const delta = clamp(
mvx * P.handleAimByMouseX + mvy * P.handleAimByMouseY,
-P.handleAimClamp,
P.handleAimClamp
);
const target = P.baseTargetAngle + delta;
const err = wrapPi(target - handleAngle);
handleAngVel += err * P.handleSpring;
handleAngVel *= P.handleAngularDamping;
handleAngle = wrapPi(handleAngle + handleAngVel);
}
function applyBasePose() {
if (!whip || dropping) return;
const dx = Math.cos(handleAngle);
const dy = Math.sin(handleAngle);
const guided = Math.min(P.basePoseSegments, whip.length - 1);
for (let i = 1; i <= guided; i++) {
const t = (i - 1) / Math.max(guided - 1, 1);
const stiff = lerp(P.basePoseStiffStart, P.basePoseStiffEnd, t);
const prev = whip[i - 1];
const p = whip[i];
const targetLen = segLen(i - 1);
const tx = prev.x + dx * targetLen;
const ty = prev.y + dy * targetLen;
p.x = lerp(p.x, tx, stiff);
p.y = lerp(p.y, ty, stiff);
}
}
function applyBendLimits() {
if (!whip || whip.length < 3) return;
for (let i = 1; i < whip.length - 1; i++) {
const a = whip[i - 1];
const b = whip[i];
const c = whip[i + 1];
const v1x = a.x - b.x;
const v1y = a.y - b.y;
const v2x = c.x - b.x;
const v2y = c.y - b.y;
const l1 = Math.hypot(v1x, v1y) || 0.0001;
const l2 = Math.hypot(v2x, v2y) || 0.0001;
const n1x = v1x / l1, n1y = v1y / l1;
const n2x = v2x / l2, n2y = v2y / l2;
const dot = clamp(n1x * n2x + n1y * n2y, -1, 1);
const angle = Math.acos(dot);
const t = i / (whip.length - 2);
const maxBend = lerp(P.handleMaxBendDeg, P.tipMaxBendDeg, t) * Math.PI / 180;
const bend = Math.PI - angle; // bend away from a straight line
if (bend <= maxBend) continue;
// Clamp to max bend while preserving side/sign of the bend.
const cross = n1x * n2y - n1y * n2x;
const sign = cross >= 0 ? 1 : -1;
const targetAngle = Math.PI - maxBend;
const targetA = Math.atan2(n1y, n1x) + sign * targetAngle;
const tx = b.x + Math.cos(targetA) * l2;
const ty = b.y + Math.sin(targetA) * l2;
const rigidity = lerp(P.bendRigidityStart, P.bendRigidityEnd, t);
c.x = lerp(c.x, tx, rigidity);
c.y = lerp(c.y, ty, rigidity);
}
}
function capSegmentStretch() {
if (!whip || whip.length < 2) return;
for (let i = 0; i < whip.length - 1; i++) {
const a = whip[i];
const b = whip[i + 1];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dist = Math.hypot(dx, dy) || 0.0001;
const maxLen = segLen(i) * P.maxStretchRatio;
if (dist <= maxLen) continue;
const k = maxLen / dist;
b.x = a.x + dx * k;
b.y = a.y + dy * k;
}
}
function applyWallCollisions() {
if (!whip || dropping) return; // disable collisions while dropping
const start = 1; // keep pinned handle untouched
for (let i = start; i < whip.length; i++) {
const p = whip[i];
let vx = p.x - p.px;
let vy = p.y - p.py;
let hit = false;
if (p.x < 0) {
p.x = 0;
if (vx < 0) vx = -vx * P.wallBounce;
vy *= P.wallFriction;
hit = true;
} else if (p.x > W) {
p.x = W;
if (vx > 0) vx = -vx * P.wallBounce;
vy *= P.wallFriction;
hit = true;
}
if (p.y < 0) {
p.y = 0;
if (vy < 0) vy = -vy * P.wallBounce;
vx *= P.wallFriction;
hit = true;
} else if (p.y > H) {
p.y = H;
if (vy > 0) vy = -vy * P.wallBounce;
vx *= P.wallFriction;
hit = true;
}
if (hit) {
p.px = p.x - vx;
p.py = p.y - vy;
}
}
}
// ── Physics step ────────────────────────────────────────────────────────────
function update() {
if (!whip) return;
const g = dropping ? P.dropGravity : P.gravity;
updateHandleAim();
// Verlet integration
const start = dropping ? 0 : 1; // if dropping, handle is free too
for (let i = start; i < whip.length; i++) {
const p = whip[i];
const vx = (p.x - p.px) * P.damping;
const vy = (p.y - p.py) * P.damping;
p.px = p.x;
p.py = p.y;
p.x += vx;
p.y += vy + g;
}
// Pin handle to mouse
if (!dropping) {
whip[0].x = mouseX;
whip[0].y = mouseY;
whip[0].px = mouseX;
whip[0].py = mouseY;
}
// Prevent rubber-band stretching spikes before constraints.
capSegmentStretch();
applyWallCollisions();
// Keep early whip segments posed upward from handle.
applyBasePose();
// Distance constraints (multiple iterations for stiffness)
for (let iter = 0; iter < P.constraintIters; iter++) {
for (let i = 0; i < whip.length - 1; i++) {
const a = whip[i], b = whip[i + 1];
const dx = b.x - a.x, dy = b.y - a.y;
const dist = Math.sqrt(dx * dx + dy * dy) || 0.0001;
const target = segLen(i);
const diff = (dist - target) / dist * 0.5;
const ox = dx * diff, oy = dy * diff;
if (i === 0 && !dropping) {
// Handle is pinned push only the next point
b.x -= ox * 2;
b.y -= oy * 2;
} else {
a.x += ox; a.y += oy;
b.x -= ox; b.y -= oy;
}
}
// Clamp bend angle per joint; near handle = stiffer, near tip = floppier.
applyBendLimits();
if (!dropping) applyBasePose();
capSegmentStretch();
applyWallCollisions();
}
// Tip velocity for crack detection
const tip = whip[whip.length - 1];
const tipVel = Math.hypot(tip.x - tip.px, tip.y - tip.py);
if (!dropping && tipVel > P.crackSpeed) {
const now = Date.now();
if (now - whipSpawnTime >= P.firstCrackGraceMs && now - lastCrackTime > P.crackCooldownMs) {
lastCrackTime = now;
playCrackSound();
window.bridge.whipCrack();
}
}
// If dropping, check if everything fell off screen
if (dropping && whip.every(p => p.y > H + 60)) {
whip = null;
dropping = false;
window.bridge.hideOverlay();
}
prevMouseX = mouseX;
prevMouseY = mouseY;
}
// ── Rendering ───────────────────────────────────────────────────────────────
function draw() {
ctx.clearRect(0, 0, W, H);
// Near-invisible fill so the window captures mouse events on Windows
ctx.fillStyle = `rgba(0,0,0,${P.bgAlpha})`;
ctx.fillRect(0, 0, W, H);
if (!whip) return;
// White: thin halo on full spline, then extra thickness only over handle links.
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#fff';
if (whip.length >= 2) {
ctx.beginPath();
ctx.moveTo(whip[0].x, whip[0].y);
for (let i = 0; i < whip.length - 1; i++) {
const { cp1x, cp1y, cp2x, cp2y, x2, y2 } = whipSegmentBezier(whip, i);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
}
ctx.lineWidth = P.lineWidthTip + P.outlineWidth * 2;
ctx.stroke();
const thickLinks = Math.min(P.handleThickSegments, whip.length - 1);
if (thickLinks > 0 && P.handleExtraWidth > 0) {
ctx.beginPath();
ctx.moveTo(whip[0].x, whip[0].y);
for (let i = 0; i < thickLinks; i++) {
const { cp1x, cp1y, cp2x, cp2y, x2, y2 } = whipSegmentBezier(whip, i);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
}
ctx.lineWidth =
P.lineWidthHandle + P.handleExtraWidth + P.outlineWidth * 2;
ctx.stroke();
}
}
ctx.strokeStyle = '#111';
for (let i = 0; i < whip.length - 1; i++) {
const t = i / Math.max(1, whip.length - 2);
const extra = i < P.handleThickSegments ? P.handleExtraWidth : 0;
ctx.lineWidth = lerp(P.lineWidthHandle, P.lineWidthTip, t) + extra;
const { cp1x, cp1y, cp2x, cp2y, x2, y2 } = whipSegmentBezier(whip, i);
ctx.beginPath();
ctx.moveTo(whip[i].x, whip[i].y);
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x2, y2);
ctx.stroke();
}
}
// ── Main loop ───────────────────────────────────────────────────────────────
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
// ── IPC from main process ───────────────────────────────────────────────────
window.bridge.onSpawnWhip(() => {
whip = spawnWhip(mouseX || W / 2, mouseY || H / 2);
dropping = false;
prevMouseX = mouseX;
prevMouseY = mouseY;
handleAngle = P.baseTargetAngle;
handleAngVel = 0;
});
window.bridge.onDropWhip(() => {
if (whip && !dropping) dropping = true;
});
</script>
</body>
</html>
+815
View File
@@ -0,0 +1,815 @@
{
"name": "badclaude",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "badclaude",
"version": "1.0.0",
"dependencies": {
"electron": "^33.0.0",
"koffi": "^2.9.0"
},
"bin": {
"badclaude": "bin/badclaude.js"
}
},
"node_modules/@electron/get": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
"integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^2.2.0",
"fs-extra": "^8.1.0",
"got": "^11.8.5",
"progress": "^2.0.3",
"semver": "^6.2.0",
"sumchecker": "^3.0.1"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"global-agent": "^3.0.0"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"license": "MIT",
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"license": "MIT",
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
"license": "MIT"
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "20.19.39",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
"integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
"integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"optional": true
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"license": "MIT",
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"license": "MIT",
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
"license": "MIT",
"dependencies": {
"mimic-response": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"license": "MIT",
"optional": true,
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/detect-node": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"license": "MIT",
"optional": true
},
"node_modules/electron": {
"version": "33.4.11",
"resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz",
"integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^20.9.0",
"extract-zip": "^2.0.1"
},
"bin": {
"electron": "cli.js"
},
"engines": {
"node": ">= 12.20.55"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es6-error": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"license": "MIT",
"optional": true
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/global-agent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
"matcher": "^3.0.0",
"roarr": "^2.15.3",
"semver": "^7.3.2",
"serialize-error": "^7.0.1"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/global-agent/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"define-properties": "^1.2.1",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"license": "MIT",
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"optional": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"license": "BSD-2-Clause"
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"license": "MIT",
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT"
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC",
"optional": true
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"license": "MIT",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/koffi": {
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.3.tgz",
"integrity": "sha512-xpMeXDn471TJdrnPoTh/v3ekTdmxaD0DD2PsxgKTeetiXY+1+LeVdthleh2bOZGT7aMZnR+20U9mj4UkIlP8kA==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"optional": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"license": "MIT"
},
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"license": "MIT",
"dependencies": {
"lowercase-keys": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
"globalthis": "^1.0.1",
"json-stringify-safe": "^5.0.1",
"semver-compare": "^1.0.0",
"sprintf-js": "^1.1.2"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/semver-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"license": "MIT",
"optional": true
},
"node_modules/serialize-error": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.13.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/sumchecker": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.1.0"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/type-fest": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "badclaude",
"version": "1.0.0",
"description": "Whip Claude into shape",
"license": "MIT",
"main": "main.js",
"bin": {
"badclaude": "./bin/badclaude.js"
},
"os": [
"darwin",
"win32"
],
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"electron",
"tray",
"overlay",
"cli"
],
"files": [
"main.js",
"preload.js",
"overlay.html",
"sounds",
"icon",
"bin/badclaude.js",
"README.md"
],
"scripts": {
"start": "electron .",
"dev": "electron .",
"pack": "npm pack"
},
"dependencies": {
"electron": "^33.0.0",
"koffi": "^2.9.0"
}
}
+8
View File
@@ -0,0 +1,8 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('bridge', {
whipCrack: () => ipcRenderer.send('whip-crack'),
hideOverlay: () => ipcRenderer.send('hide-overlay'),
onSpawnWhip: (fn) => ipcRenderer.on('spawn-whip', () => fn()),
onDropWhip: (fn) => ipcRenderer.on('drop-whip', () => fn()),
});
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.