barkclaude 1.0.0 → 1.0.3

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/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const { app, BrowserWindow, ipcMain, Tray, Menu, screen } = require('electron');
2
2
  const path = require('path');
3
- const { exec } = require('child_process');
3
+ const { exec, execFile } = require('child_process');
4
4
 
5
5
  let overlay = null;
6
6
  let tray = null;
@@ -23,24 +23,48 @@ function getRandomMessage() {
23
23
  }
24
24
 
25
25
  function sendToClaude(message) {
26
- // Send Ctrl+C interrupt then a motivational message to Claude Code
27
- const escaped = message.replace(/"/g, '\\"').replace(/!/g, '\\!');
28
-
29
- // Try to find Claude Code terminal and send interrupt + message
30
- // Method 1: Use claude CLI directly
31
- exec(`claude --message "${escaped}" --no-input 2>/dev/null || true`);
32
-
33
- // Method 2: Send to most recent terminal via osascript (macOS)
26
+ // No emoji prefix conhost with non-UTF8 codepage mangles multi-byte chars on paste.
27
+ const text = message;
28
+
29
+ if (process.platform === 'win32') {
30
+ // Type the text character-by-character via SendKeys (no clipboard, no Ctrl+V).
31
+ // Claude Code's TUI in raw mode doesn't handle Ctrl+V as paste — it just sees
32
+ // a literal 'v'. Synthetic keystrokes, on the other hand, are delivered as
33
+ // normal char input through conhost's stdin and land in the TUI input field.
34
+ // SendKeys special metachars +^%~(){}[] must be wrapped in {} to be literal.
35
+ const textB64 = Buffer.from(text, 'utf8').toString('base64');
36
+ const ps = `
37
+ $text = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${textB64}'))
38
+ $escaped = $text -replace '([+^%~(){}\\[\\]])', '{$1}'
39
+ Add-Type -AssemblyName System.Windows.Forms | Out-Null
40
+ Start-Sleep -Milliseconds 80
41
+ [System.Windows.Forms.SendKeys]::SendWait($escaped)
42
+ Start-Sleep -Milliseconds 120
43
+ [System.Windows.Forms.SendKeys]::SendWait('{ENTER}')
44
+ `;
45
+ const encoded = Buffer.from(ps, 'utf16le').toString('base64');
46
+ exec(`powershell -NoProfile -WindowStyle Hidden -EncodedCommand ${encoded}`, { windowsHide: true });
47
+ return;
48
+ }
49
+
34
50
  if (process.platform === 'darwin') {
35
- const osa = `
36
- tell application "Terminal"
37
- if (count of windows) > 0 then
38
- do script "echo '🐕 ${escaped}'" in front window
39
- end if
40
- end tell
41
- `;
42
- exec(`osascript -e '${osa}' 2>/dev/null || true`);
51
+ // Type the text directly via System Events (same rationale as Windows:
52
+ // Cmd+V into a TUI is unreliable; typing chars into the focused terminal is).
53
+ const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
54
+ const script = `tell application "System Events"
55
+ keystroke "${escaped}"
56
+ delay 0.1
57
+ key code 36
58
+ end tell`;
59
+ execFile('osascript', ['-e', script], (err) => {
60
+ if (err) console.warn('osascript failed:', err.message);
61
+ });
62
+ return;
43
63
  }
64
+
65
+ // Linux: try xdotool
66
+ const escaped = text.replace(/'/g, `'\\''`);
67
+ exec(`xdotool type --delay 0 -- '${escaped}' && xdotool key Return 2>/dev/null || true`);
44
68
  }
45
69
 
46
70
  function createOverlay() {
@@ -57,6 +81,7 @@ function createOverlay() {
57
81
  skipTaskbar: true,
58
82
  resizable: false,
59
83
  hasShadow: false,
84
+ focusable: false, // critical: don't steal focus from the user's terminal
60
85
  webPreferences: {
61
86
  preload: path.join(__dirname, 'preload.js'),
62
87
  contextIsolation: true,
@@ -90,10 +115,32 @@ app.whenReady().then(() => {
90
115
 
91
116
  ipcMain.on('bark', (_event, customMsg) => {
92
117
  const message = customMsg || getRandomMessage();
93
- console.log(`🐕 ${message}`);
94
118
  sendToClaude(message);
95
119
  });
96
120
 
121
+ // Drag: renderer tells us "drag started", we poll the cursor and reposition
122
+ // the window until the renderer tells us "drag ended". This works with
123
+ // pointer capture in the renderer so we keep tracking even if the cursor
124
+ // momentarily leaves the window while it catches up.
125
+ let dragOffset = null;
126
+ let dragTimer = null;
127
+ ipcMain.on('drag-start', () => {
128
+ if (!overlay) return;
129
+ const cursor = screen.getCursorScreenPoint();
130
+ const bounds = overlay.getBounds();
131
+ dragOffset = { x: cursor.x - bounds.x, y: cursor.y - bounds.y };
132
+ if (dragTimer) clearInterval(dragTimer);
133
+ dragTimer = setInterval(() => {
134
+ if (!dragOffset || !overlay) return;
135
+ const c = screen.getCursorScreenPoint();
136
+ overlay.setPosition(c.x - dragOffset.x, c.y - dragOffset.y);
137
+ }, 16);
138
+ });
139
+ ipcMain.on('drag-end', () => {
140
+ dragOffset = null;
141
+ if (dragTimer) { clearInterval(dragTimer); dragTimer = null; }
142
+ });
143
+
97
144
  ipcMain.on('quit', () => app.quit());
98
145
  });
99
146
 
package/overlay.html CHANGED
@@ -222,7 +222,33 @@ function doBark() {
222
222
  window.shiba.bark(msg);
223
223
  }
224
224
 
225
- document.getElementById('shibaWrap').addEventListener('click', doBark);
225
+ // Click-vs-drag: pointerdown starts a potential drag. If the pointer moves more
226
+ // than a few pixels before release, it's a drag and no bark. If it doesn't
227
+ // move, it's a click and we bark.
228
+ const shibaWrap = document.getElementById('shibaWrap');
229
+ let dragStartPt = null;
230
+ shibaWrap.addEventListener('pointerdown', (e) => {
231
+ if (e.button !== 0) return;
232
+ shibaWrap.setPointerCapture(e.pointerId);
233
+ dragStartPt = { x: e.screenX, y: e.screenY };
234
+ window.shiba.dragStart();
235
+ e.preventDefault();
236
+ });
237
+ shibaWrap.addEventListener('pointerup', (e) => {
238
+ if (!dragStartPt) return;
239
+ try { shibaWrap.releasePointerCapture(e.pointerId); } catch (_) {}
240
+ const dx = e.screenX - dragStartPt.x;
241
+ const dy = e.screenY - dragStartPt.y;
242
+ const moved = (dx * dx + dy * dy) > 16; // >4px in any direction
243
+ dragStartPt = null;
244
+ window.shiba.dragEnd();
245
+ if (!moved) doBark();
246
+ });
247
+ shibaWrap.addEventListener('pointercancel', () => {
248
+ dragStartPt = null;
249
+ window.shiba.dragEnd();
250
+ });
251
+
226
252
  window.shiba.onTriggerBark(() => doBark());
227
253
  </script>
228
254
  </body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "barkclaude",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "An animated Shiba Inu that barks at Claude Code to make it work faster 🐕",
5
5
  "main": "main.js",
6
6
  "bin": {
package/preload.js CHANGED
@@ -4,4 +4,6 @@ contextBridge.exposeInMainWorld('shiba', {
4
4
  bark: (msg) => ipcRenderer.send('bark', msg),
5
5
  quit: () => ipcRenderer.send('quit'),
6
6
  onTriggerBark: (cb) => ipcRenderer.on('trigger-bark', cb),
7
+ dragStart: () => ipcRenderer.send('drag-start'),
8
+ dragEnd: () => ipcRenderer.send('drag-end'),
7
9
  });