barkclaude 1.0.1 → 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,40 +23,42 @@ function getRandomMessage() {
23
23
  }
24
24
 
25
25
  function sendToClaude(message) {
26
- const text = `🐕 ${message}`;
26
+ // No emoji prefix — conhost with non-UTF8 codepage mangles multi-byte chars on paste.
27
+ const text = message;
27
28
 
28
29
  if (process.platform === 'win32') {
29
- // Put text on clipboard, focus a likely terminal window, paste + Enter.
30
- // Uses PowerShell -EncodedCommand so we don't have to escape anything.
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');
31
36
  const ps = `
37
+ $text = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${textB64}'))
38
+ $escaped = $text -replace '([+^%~(){}\\[\\]])', '{$1}'
32
39
  Add-Type -AssemblyName System.Windows.Forms | Out-Null
33
- [System.Windows.Forms.Clipboard]::SetText([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${Buffer.from(text, 'utf8').toString('base64')}')))
34
- $wshell = New-Object -ComObject wscript.shell
35
- $titles = @('Claude Code','Windows Terminal','PowerShell','Command Prompt','cmd','Terminal','WezTerm','Alacritty','Cursor','VSCode','Visual Studio Code')
36
- $activated = $false
37
- foreach ($t in $titles) { if ($wshell.AppActivate($t)) { $activated = $true; break } }
38
- if (-not $activated) { exit 0 }
39
- Start-Sleep -Milliseconds 200
40
- [System.Windows.Forms.SendKeys]::SendWait('^v')
41
40
  Start-Sleep -Milliseconds 80
41
+ [System.Windows.Forms.SendKeys]::SendWait($escaped)
42
+ Start-Sleep -Milliseconds 120
42
43
  [System.Windows.Forms.SendKeys]::SendWait('{ENTER}')
43
44
  `;
44
45
  const encoded = Buffer.from(ps, 'utf16le').toString('base64');
45
- exec(`powershell -NoProfile -EncodedCommand ${encoded}`);
46
+ exec(`powershell -NoProfile -WindowStyle Hidden -EncodedCommand ${encoded}`, { windowsHide: true });
46
47
  return;
47
48
  }
48
49
 
49
50
  if (process.platform === 'darwin') {
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).
50
53
  const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
51
- const osa = `
52
- tell application "System Events"
53
- set the clipboard to "${escaped}"
54
- keystroke "v" using command down
55
- delay 0.1
56
- keystroke return
57
- end tell
58
- `;
59
- exec(`osascript -e '${osa}' 2>/dev/null || true`);
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
+ });
60
62
  return;
61
63
  }
62
64
 
@@ -79,6 +81,7 @@ function createOverlay() {
79
81
  skipTaskbar: true,
80
82
  resizable: false,
81
83
  hasShadow: false,
84
+ focusable: false, // critical: don't steal focus from the user's terminal
82
85
  webPreferences: {
83
86
  preload: path.join(__dirname, 'preload.js'),
84
87
  contextIsolation: true,
@@ -112,10 +115,32 @@ app.whenReady().then(() => {
112
115
 
113
116
  ipcMain.on('bark', (_event, customMsg) => {
114
117
  const message = customMsg || getRandomMessage();
115
- console.log(`🐕 ${message}`);
116
118
  sendToClaude(message);
117
119
  });
118
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
+
119
144
  ipcMain.on('quit', () => app.quit());
120
145
  });
121
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.1",
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
  });