claude-dock 1.0.2 → 1.2.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 (3) hide show
  1. package/bin/cli.js +5 -0
  2. package/init.lua +457 -26
  3. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -159,6 +159,11 @@ async function main() {
159
159
  log(` ${colors.dim}⌘⌥T${colors.reset} Toggle dock`);
160
160
  log(` ${colors.dim}⌘⌥N${colors.reset} Add new terminal`);
161
161
  log(` ${colors.dim}⌘⌥R${colors.reset} Reload config`);
162
+ log(` ${colors.dim}⌘⌥L${colors.reset} Move macOS Dock to left`);
163
+ log(` ${colors.dim}⌘⌥B${colors.reset} Move macOS Dock to bottom`);
164
+ log('');
165
+ log(`${colors.yellow}Tip:${colors.reset} If your macOS Dock is at the bottom, you'll be prompted to move it.`);
166
+ log(` You can change this anytime with ${colors.dim}⌘⌥L${colors.reset} (left) or ${colors.dim}⌘⌥B${colors.reset} (bottom).`);
162
167
  log('');
163
168
  }
164
169
 
package/init.lua CHANGED
@@ -13,8 +13,8 @@ local config = {
13
13
  addButtonWidth = 40,
14
14
  utilityButtonWidth = 28,
15
15
  initialSlots = 3,
16
- elementsPerSlot = 4, -- bg, border, title, status
17
- baseElements = 4, -- dock bg + border + minimize btn + minimize text
16
+ elementsPerSlot = 5, -- bg, border, title, status, notification badge
17
+ baseElements = 6, -- dock bg + border + help btn + help text + minimize btn + minimize text
18
18
  windowCaptureDelay = 0.3,
19
19
  windowCaptureRetries = 5,
20
20
  colors = {
@@ -30,7 +30,11 @@ local config = {
30
30
  addBtnText = { red = 0.6, green = 0.8, blue = 0.6, alpha = 1 },
31
31
  minBtnBg = { red = 0.18, green = 0.18, blue = 0.25, alpha = 1 },
32
32
  minBtnText = { red = 0.6, green = 0.6, blue = 0.9, alpha = 1 },
33
- }
33
+ helpBtnBg = { red = 0.2, green = 0.18, blue = 0.12, alpha = 1 },
34
+ helpBtnText = { red = 0.9, green = 0.8, blue = 0.5, alpha = 1 },
35
+ notificationBadge = { red = 1, green = 0.3, blue = 0.3, alpha = 1 },
36
+ },
37
+ notificationBadgeSize = 12,
34
38
  }
35
39
 
36
40
  -- State
@@ -38,6 +42,28 @@ local slotCount = config.initialSlots
38
42
  local slots = {}
39
43
  local dock = nil
40
44
  local tooltip = nil
45
+ local helpPanel = nil
46
+ local macOSDockAtBottom = false
47
+
48
+ -- Check macOS dock position
49
+ local function getMacOSDockPosition()
50
+ local output, status = hs.execute("defaults read com.apple.dock orientation 2>/dev/null")
51
+ if status and output then
52
+ output = output:gsub("%s+", "")
53
+ if output == "left" or output == "right" then
54
+ return output
55
+ end
56
+ end
57
+ return "bottom" -- default
58
+ end
59
+
60
+ -- Get macOS dock size (tile size + magnification consideration)
61
+ local function getMacOSDockHeight()
62
+ local tileSize = hs.execute("defaults read com.apple.dock tilesize 2>/dev/null")
63
+ tileSize = tonumber(tileSize) or 48
64
+ -- Add padding for dock chrome and gaps
65
+ return tileSize + 20
66
+ end
41
67
 
42
68
  -- Resource handles (for cleanup on reload)
43
69
  local windowFilter = nil
@@ -66,13 +92,17 @@ local function cleanup()
66
92
  tooltip:delete()
67
93
  tooltip = nil
68
94
  end
95
+ if helpPanel then
96
+ helpPanel:delete()
97
+ helpPanel = nil
98
+ end
69
99
  end
70
100
  cleanup() -- Clean up any previous instance
71
101
 
72
102
  local function initSlots()
73
103
  for i = 1, slotCount do
74
104
  if not slots[i] then
75
- slots[i] = { windowId = nil, customName = nil, pending = false }
105
+ slots[i] = { windowId = nil, customName = nil, pending = false, hasNotification = false }
76
106
  end
77
107
  end
78
108
  end
@@ -96,9 +126,14 @@ local function getDockFrame()
96
126
  local frame = screen:fullFrame()
97
127
  local dockWidth = getDockWidth()
98
128
  local dockHeight = getDockHeight()
129
+ local bottomOffset = config.bottomOffset
130
+ -- Add extra offset if macOS dock is at bottom
131
+ if macOSDockAtBottom then
132
+ bottomOffset = bottomOffset + getMacOSDockHeight()
133
+ end
99
134
  return {
100
135
  x = (frame.w - dockWidth) / 2,
101
- y = frame.h - dockHeight - config.bottomOffset,
136
+ y = frame.h - dockHeight - bottomOffset,
102
137
  w = dockWidth,
103
138
  h = dockHeight
104
139
  }
@@ -119,6 +154,33 @@ local function getWindowTitle(win)
119
154
  return title
120
155
  end
121
156
 
157
+ -- Find slot index by window ID
158
+ local function findSlotByWindowId(windowId)
159
+ if not windowId then return nil end
160
+ for i, slot in ipairs(slots) do
161
+ if slot.windowId == windowId then
162
+ return i
163
+ end
164
+ end
165
+ return nil
166
+ end
167
+
168
+ -- Set notification for a slot (only if window is not focused)
169
+ local function setSlotNotification(slotIndex)
170
+ local slot = slots[slotIndex]
171
+ if not slot then return end
172
+
173
+ local win = getWindow(slot.windowId)
174
+ if win then
175
+ local focusedWin = hs.window.focusedWindow()
176
+ -- Only show notification if window is not currently focused
177
+ if not focusedWin or focusedWin:id() ~= slot.windowId then
178
+ slot.hasNotification = true
179
+ updateSlotDisplay(slotIndex)
180
+ end
181
+ end
182
+ end
183
+
122
184
  -- Tooltip helpers
123
185
  local function showTooltipAt(text, x, y)
124
186
  if tooltip then tooltip:delete() end
@@ -155,11 +217,11 @@ local function showButtonTooltip(text, buttonId)
155
217
  local tipHeight = 24
156
218
  local btnX, btnWidth
157
219
 
158
- if buttonId == "minBtn" then
159
- -- Left side utility button
220
+ if buttonId == "minBtn" or buttonId == "helpBtn" then
221
+ -- Left side utility buttons
160
222
  btnX = dockFrame.x + config.margin
161
223
  btnWidth = config.utilityButtonWidth
162
- else
224
+ elseif buttonId == "addBtn" then
163
225
  -- Right side add button
164
226
  btnX = dockFrame.x + config.margin + config.utilityButtonWidth + config.gap + (slotCount * config.slotWidth) + (slotCount * config.gap)
165
227
  btnWidth = config.addButtonWidth
@@ -178,6 +240,128 @@ local function hideTooltip()
178
240
  end
179
241
  end
180
242
 
243
+ -- Help panel
244
+ local function hideHelpPanel()
245
+ if helpPanel then
246
+ helpPanel:delete()
247
+ helpPanel = nil
248
+ end
249
+ end
250
+
251
+ local function showHelpPanel()
252
+ if helpPanel then
253
+ hideHelpPanel()
254
+ return
255
+ end
256
+
257
+ local screen = hs.screen.mainScreen()
258
+ if not screen then return end
259
+ local screenFrame = screen:fullFrame()
260
+
261
+ local panelWidth = 280
262
+ local panelHeight = 260
263
+ local panelX = (screenFrame.w - panelWidth) / 2
264
+ local panelY = (screenFrame.h - panelHeight) / 2
265
+
266
+ helpPanel = hs.canvas.new({ x = panelX, y = panelY, w = panelWidth, h = panelHeight })
267
+
268
+ -- Background
269
+ helpPanel:appendElements({
270
+ type = "rectangle",
271
+ action = "fill",
272
+ roundedRectRadii = { xRadius = 12, yRadius = 12 },
273
+ fillColor = { red = 0.1, green = 0.1, blue = 0.1, alpha = 0.95 },
274
+ frame = { x = 0, y = 0, w = "100%", h = "100%" },
275
+ })
276
+
277
+ -- Border
278
+ helpPanel:appendElements({
279
+ type = "rectangle",
280
+ action = "stroke",
281
+ roundedRectRadii = { xRadius = 12, yRadius = 12 },
282
+ strokeColor = { red = 1, green = 1, blue = 1, alpha = 0.2 },
283
+ strokeWidth = 1,
284
+ frame = { x = 0, y = 0, w = "100%", h = "100%" },
285
+ })
286
+
287
+ -- Title
288
+ helpPanel:appendElements({
289
+ type = "text",
290
+ frame = { x = 0, y = 15, w = panelWidth, h = 24 },
291
+ text = "Claude Dock Shortcuts",
292
+ textAlignment = "center",
293
+ textColor = { red = 1, green = 1, blue = 1, alpha = 1 },
294
+ textSize = 16,
295
+ textFont = ".AppleSystemUIFontBold",
296
+ })
297
+
298
+ -- Shortcuts list
299
+ local shortcuts = {
300
+ { key = "⌘⌥T", desc = "Toggle dock" },
301
+ { key = "⌘⌥N", desc = "Add new terminal" },
302
+ { key = "⌘⌥M", desc = "Minimize all terminals" },
303
+ { key = "⌘⌥R", desc = "Reload config" },
304
+ { key = "⌘⌥L", desc = "Move macOS Dock left" },
305
+ { key = "⌘⌥B", desc = "Move macOS Dock bottom" },
306
+ { key = "⌥+Click", desc = "Rename slot" },
307
+ }
308
+
309
+ local startY = 50
310
+ for i, shortcut in ipairs(shortcuts) do
311
+ local y = startY + ((i - 1) * 24)
312
+ helpPanel:appendElements({
313
+ type = "text",
314
+ frame = { x = 20, y = y, w = 70, h = 20 },
315
+ text = shortcut.key,
316
+ textAlignment = "left",
317
+ textColor = { red = 0.6, green = 0.8, blue = 1, alpha = 1 },
318
+ textSize = 13,
319
+ textFont = ".AppleSystemUIFont",
320
+ })
321
+ helpPanel:appendElements({
322
+ type = "text",
323
+ frame = { x = 95, y = y, w = 170, h = 20 },
324
+ text = shortcut.desc,
325
+ textAlignment = "left",
326
+ textColor = { red = 0.8, green = 0.8, blue = 0.8, alpha = 1 },
327
+ textSize = 13,
328
+ textFont = ".AppleSystemUIFont",
329
+ })
330
+ end
331
+
332
+ -- Close button
333
+ local closeBtnY = panelHeight - 45
334
+ helpPanel:appendElements({
335
+ type = "rectangle",
336
+ action = "fill",
337
+ frame = { x = (panelWidth - 80) / 2, y = closeBtnY, w = 80, h = 30 },
338
+ roundedRectRadii = { xRadius = 6, yRadius = 6 },
339
+ fillColor = { red = 0.25, green = 0.25, blue = 0.25, alpha = 1 },
340
+ trackMouseUp = true,
341
+ id = "closeBtn",
342
+ })
343
+ helpPanel:appendElements({
344
+ type = "text",
345
+ frame = { x = (panelWidth - 80) / 2, y = closeBtnY + 6, w = 80, h = 20 },
346
+ text = "Close",
347
+ textAlignment = "center",
348
+ textColor = { red = 0.9, green = 0.9, blue = 0.9, alpha = 1 },
349
+ textSize = 13,
350
+ textFont = ".AppleSystemUIFont",
351
+ trackMouseUp = true,
352
+ id = "closeBtn",
353
+ })
354
+
355
+ helpPanel:mouseCallback(function(_, event, id)
356
+ if event == "mouseUp" and id == "closeBtn" then
357
+ hideHelpPanel()
358
+ end
359
+ end)
360
+
361
+ helpPanel:level(hs.canvas.windowLevels.modalPanel)
362
+ helpPanel:show()
363
+ end
364
+
181
365
  -- Forward declarations
182
366
  local createDock
183
367
  local updateAllSlots
@@ -203,16 +387,19 @@ local function updateSlotDisplay(slotIndex)
203
387
  status = "active"
204
388
  bgColor = config.colors.slotActive
205
389
  end
390
+ elseif slot.windowId then
391
+ -- Window exists but not visible (probably on another space)
392
+ title = slot.customName or "Terminal"
393
+ status = "(other space)"
394
+ bgColor = config.colors.slotMinimized
206
395
  else
207
- -- Don't clear customName if we're waiting for a window to spawn
208
- if not slot.pending then
209
- slot.windowId = nil
210
- slot.customName = nil
211
- title = "Empty"
212
- status = "click to open"
213
- else
396
+ -- No window assigned
397
+ if slot.pending then
214
398
  title = slot.customName or "Opening..."
215
399
  status = "launching"
400
+ else
401
+ title = "Empty"
402
+ status = "click to open"
216
403
  end
217
404
  bgColor = config.colors.slotEmpty
218
405
  end
@@ -226,6 +413,12 @@ local function updateSlotDisplay(slotIndex)
226
413
  if dock[baseIdx + 3] then
227
414
  dock[baseIdx + 3].text = status
228
415
  end
416
+ -- Update notification badge visibility
417
+ if dock[baseIdx + 4] then
418
+ dock[baseIdx + 4].fillColor = slot.hasNotification
419
+ and config.colors.notificationBadge
420
+ or { red = 0, green = 0, blue = 0, alpha = 0 }
421
+ end
229
422
  end
230
423
 
231
424
  updateAllSlots = function()
@@ -299,6 +492,9 @@ local function onSlotClick(slotIndex, isOptionClick)
299
492
  return
300
493
  end
301
494
 
495
+ -- Clear notification when clicked
496
+ slot.hasNotification = false
497
+
302
498
  local win = getWindow(slot.windowId)
303
499
  if win then
304
500
  if win:isMinimized() then
@@ -336,7 +532,7 @@ end
336
532
  -- Add a new slot and launch terminal
337
533
  local function addSlot()
338
534
  slotCount = slotCount + 1
339
- slots[slotCount] = { windowId = nil, customName = nil, pending = false }
535
+ slots[slotCount] = { windowId = nil, customName = nil, pending = false, hasNotification = false }
340
536
 
341
537
  if dock then
342
538
  dock:delete()
@@ -374,14 +570,41 @@ createDock = function()
374
570
  frame = { x = 0, y = 0, w = "100%", h = "100%" },
375
571
  })
376
572
 
377
- -- Minimize button (left side, small utility style)
378
- local minBtnX = config.margin
379
- local minBtnHeight = 28
380
- local minBtnY = config.margin + (config.slotHeight - minBtnHeight) / 2
573
+ -- Utility buttons (left side, stacked: help on top, minimize on bottom)
574
+ local utilBtnX = config.margin
575
+ local btnGap = 4
576
+ local btnHeight = (config.slotHeight - btnGap) / 2
577
+
578
+ -- Help button (top)
579
+ local helpBtnY = config.margin
381
580
  dock:appendElements({
382
581
  type = "rectangle",
383
582
  action = "fill",
384
- frame = { x = minBtnX, y = minBtnY, w = config.utilityButtonWidth, h = minBtnHeight },
583
+ frame = { x = utilBtnX, y = helpBtnY, w = config.utilityButtonWidth, h = btnHeight },
584
+ roundedRectRadii = { xRadius = 6, yRadius = 6 },
585
+ fillColor = config.colors.helpBtnBg,
586
+ trackMouseUp = true,
587
+ trackMouseEnterExit = true,
588
+ id = "helpBtn",
589
+ })
590
+ dock:appendElements({
591
+ type = "text",
592
+ frame = { x = utilBtnX, y = helpBtnY + 4, w = config.utilityButtonWidth, h = btnHeight },
593
+ text = "?",
594
+ textAlignment = "center",
595
+ textColor = config.colors.helpBtnText,
596
+ textSize = 16,
597
+ textFont = ".AppleSystemUIFontBold",
598
+ trackMouseUp = true,
599
+ id = "helpBtn",
600
+ })
601
+
602
+ -- Minimize button (bottom)
603
+ local minBtnY = config.margin + btnHeight + btnGap
604
+ dock:appendElements({
605
+ type = "rectangle",
606
+ action = "fill",
607
+ frame = { x = utilBtnX, y = minBtnY, w = config.utilityButtonWidth, h = btnHeight },
385
608
  roundedRectRadii = { xRadius = 6, yRadius = 6 },
386
609
  fillColor = config.colors.minBtnBg,
387
610
  trackMouseUp = true,
@@ -390,11 +613,11 @@ createDock = function()
390
613
  })
391
614
  dock:appendElements({
392
615
  type = "text",
393
- frame = { x = minBtnX, y = minBtnY + 2, w = config.utilityButtonWidth, h = minBtnHeight },
616
+ frame = { x = utilBtnX, y = minBtnY + 4, w = config.utilityButtonWidth, h = btnHeight },
394
617
  text = "⌄",
395
618
  textAlignment = "center",
396
619
  textColor = config.colors.minBtnText,
397
- textSize = 18,
620
+ textSize = 16,
398
621
  textFont = ".AppleSystemUIFont",
399
622
  trackMouseUp = true,
400
623
  id = "minBtn",
@@ -443,6 +666,16 @@ createDock = function()
443
666
  textSize = 10,
444
667
  textFont = ".AppleSystemUIFont",
445
668
  })
669
+
670
+ -- Notification badge (top-right corner of slot, overlapping edge like app badges)
671
+ local badgeSize = config.notificationBadgeSize
672
+ dock:appendElements({
673
+ type = "circle",
674
+ action = "fill",
675
+ center = { x = slotX + config.slotWidth - badgeSize/3, y = config.margin + badgeSize/3 },
676
+ radius = badgeSize / 2,
677
+ fillColor = { red = 0, green = 0, blue = 0, alpha = 0 }, -- Hidden by default
678
+ })
446
679
  end
447
680
 
448
681
  -- Add button (right side)
@@ -475,6 +708,8 @@ createDock = function()
475
708
  addSlot()
476
709
  elseif id == "minBtn" then
477
710
  minimizeAllTerminals()
711
+ elseif id == "helpBtn" then
712
+ showHelpPanel()
478
713
  elseif id and id:match("^slot") then
479
714
  local idx = tonumber(id:match("%d+"))
480
715
  if idx then
@@ -487,8 +722,10 @@ createDock = function()
487
722
  showButtonTooltip("⌘⌥N", "addBtn")
488
723
  elseif id == "minBtn" then
489
724
  showButtonTooltip("⌘⌥M", "minBtn")
725
+ elseif id == "helpBtn" then
726
+ showButtonTooltip("Help", "helpBtn")
490
727
  end
491
- elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn") then
728
+ elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn" or id == "helpBtn") then
492
729
  hideTooltip()
493
730
  end
494
731
  end)
@@ -524,12 +761,64 @@ end
524
761
  -- Window event watcher for immediate updates
525
762
  windowFilter = hs.window.filter.new("Terminal")
526
763
  windowFilter:subscribe({
527
- hs.window.filter.windowDestroyed,
528
764
  hs.window.filter.windowMinimized,
529
765
  hs.window.filter.windowUnminimized,
530
- hs.window.filter.windowFocused,
531
766
  }, updateAllSlots)
532
767
 
768
+ -- Handle window destruction - clear the slot
769
+ windowFilter:subscribe(hs.window.filter.windowDestroyed, function(win, appName, event)
770
+ -- win may be nil at this point, so we need to check all slots
771
+ for i, slot in ipairs(slots) do
772
+ if slot.windowId then
773
+ local existingWin = getWindow(slot.windowId)
774
+ if not existingWin then
775
+ -- Try to verify window is truly gone (not just on another space)
776
+ -- by checking all Terminal windows
777
+ local allTerminals = hs.window.filter.new("Terminal"):getWindows()
778
+ local found = false
779
+ for _, w in ipairs(allTerminals) do
780
+ if w:id() == slot.windowId then
781
+ found = true
782
+ break
783
+ end
784
+ end
785
+ if not found then
786
+ slot.windowId = nil
787
+ slot.customName = nil
788
+ slot.hasNotification = false
789
+ end
790
+ end
791
+ end
792
+ end
793
+ updateAllSlots()
794
+ end)
795
+
796
+ -- Clear notification when window is focused
797
+ windowFilter:subscribe(hs.window.filter.windowFocused, function(win)
798
+ if win then
799
+ local slotIndex = findSlotByWindowId(win:id())
800
+ if slotIndex then
801
+ slots[slotIndex].hasNotification = false
802
+ end
803
+ end
804
+ updateAllSlots()
805
+ end)
806
+
807
+ -- Watch for window title changes (indicates terminal activity)
808
+ windowFilter:subscribe(hs.window.filter.windowTitleChanged, function(win)
809
+ if win then
810
+ local slotIndex = findSlotByWindowId(win:id())
811
+ if slotIndex then
812
+ local focusedWin = hs.window.focusedWindow()
813
+ -- Only show notification if this window isn't focused
814
+ if not focusedWin or focusedWin:id() ~= win:id() then
815
+ slots[slotIndex].hasNotification = true
816
+ updateSlotDisplay(slotIndex)
817
+ end
818
+ end
819
+ end
820
+ end)
821
+
533
822
  -- Periodic refresh as fallback
534
823
  updateTimer = hs.timer.doEvery(2, function()
535
824
  if dock and dock:isShowing() then
@@ -548,7 +837,53 @@ screenWatcher = hs.screen.watcher.new(function()
548
837
  end)
549
838
  screenWatcher:start()
550
839
 
840
+ -- Move macOS dock position
841
+ local function moveMacOSDockLeft()
842
+ hs.execute("defaults write com.apple.dock orientation left && killall Dock")
843
+ macOSDockAtBottom = false
844
+ hs.alert.show("macOS Dock moved to left")
845
+ -- Reposition Claude Dock
846
+ if dock then
847
+ local frame = getDockFrame()
848
+ if frame then dock:frame(frame) end
849
+ end
850
+ end
851
+
852
+ local function moveMacOSDockBottom()
853
+ hs.execute("defaults write com.apple.dock orientation bottom && killall Dock")
854
+ macOSDockAtBottom = true
855
+ hs.alert.show("macOS Dock moved to bottom")
856
+ -- Reposition Claude Dock
857
+ if dock then
858
+ local frame = getDockFrame()
859
+ if frame then dock:frame(frame) end
860
+ end
861
+ end
862
+
863
+ -- Check macOS dock and prompt user if at bottom
864
+ -- Returns true if user chose to keep dock at bottom
865
+ local function checkMacOSDockOnStartup()
866
+ local position = getMacOSDockPosition()
867
+ if position == "bottom" then
868
+ local button = hs.dialog.blockAlert(
869
+ "macOS Dock Position",
870
+ "Your macOS Dock is at the bottom of the screen, which may overlap with Claude Dock.\n\nWould you like to move it to the left side?",
871
+ "Move to Left",
872
+ "Keep at Bottom"
873
+ )
874
+ if button == "Move to Left" then
875
+ moveMacOSDockLeft()
876
+ return false
877
+ else
878
+ macOSDockAtBottom = true
879
+ return true
880
+ end
881
+ end
882
+ return false
883
+ end
884
+
551
885
  -- Initialize
886
+ local showRepositionedMsg = checkMacOSDockOnStartup()
552
887
  createDock()
553
888
 
554
889
  -- Hotkeys
@@ -556,8 +891,35 @@ hs.hotkey.bind({"cmd", "alt"}, "T", toggleDock)
556
891
  hs.hotkey.bind({"cmd", "alt"}, "N", addSlot)
557
892
  hs.hotkey.bind({"cmd", "alt"}, "M", minimizeAllTerminals)
558
893
  hs.hotkey.bind({"cmd", "alt"}, "R", hs.reload)
894
+ hs.hotkey.bind({"cmd", "alt"}, "L", moveMacOSDockLeft)
895
+ hs.hotkey.bind({"cmd", "alt"}, "B", moveMacOSDockBottom)
559
896
 
560
897
  hs.alert.show("Claude Dock Ready")
898
+ if showRepositionedMsg then
899
+ hs.timer.doAfter(1.5, function()
900
+ hs.alert.show("Positioned above macOS Dock")
901
+ end)
902
+ end
903
+
904
+ -- Global function to trigger notification on a slot (for testing or external use)
905
+ -- Usage: triggerNotification(1) to trigger on slot 1
906
+ function triggerNotification(slotIndex)
907
+ if slotIndex and slots[slotIndex] then
908
+ setSlotNotification(slotIndex)
909
+ return true
910
+ end
911
+ return false
912
+ end
913
+
914
+ -- Global function to clear notification on a slot
915
+ function clearNotification(slotIndex)
916
+ if slotIndex and slots[slotIndex] then
917
+ slots[slotIndex].hasNotification = false
918
+ updateSlotDisplay(slotIndex)
919
+ return true
920
+ end
921
+ return false
922
+ end
561
923
 
562
924
  -- ===================
563
925
  -- TESTS (run with: hs -c "runTests()")
@@ -704,6 +1066,75 @@ function runTests()
704
1066
  assert(ok, "should not error with invalid windowIds")
705
1067
  end)
706
1068
 
1069
+ test("moveMacOSDockLeft is a function", function()
1070
+ assert(type(moveMacOSDockLeft) == "function", "moveMacOSDockLeft should be a function")
1071
+ end)
1072
+
1073
+ test("moveMacOSDockBottom is a function", function()
1074
+ assert(type(moveMacOSDockBottom) == "function", "moveMacOSDockBottom should be a function")
1075
+ end)
1076
+
1077
+ test("getMacOSDockPosition returns valid position", function()
1078
+ local pos = getMacOSDockPosition()
1079
+ assert(pos == "left" or pos == "right" or pos == "bottom", "position should be left, right, or bottom")
1080
+ end)
1081
+
1082
+ test("getMacOSDockHeight returns number", function()
1083
+ local height = getMacOSDockHeight()
1084
+ assert(type(height) == "number", "height should be a number")
1085
+ assert(height > 0, "height should be positive")
1086
+ end)
1087
+
1088
+ test("checkMacOSDockOnStartup is a function", function()
1089
+ assert(type(checkMacOSDockOnStartup) == "function", "checkMacOSDockOnStartup should be a function")
1090
+ end)
1091
+
1092
+ test("showHelpPanel is a function", function()
1093
+ assert(type(showHelpPanel) == "function", "showHelpPanel should be a function")
1094
+ end)
1095
+
1096
+ test("hideHelpPanel is a function", function()
1097
+ assert(type(hideHelpPanel) == "function", "hideHelpPanel should be a function")
1098
+ end)
1099
+
1100
+ -- Notification badge tests
1101
+ test("config has notification badge color", function()
1102
+ assert(config.colors.notificationBadge, "notificationBadge color should exist")
1103
+ assert(config.notificationBadgeSize, "notificationBadgeSize should exist")
1104
+ end)
1105
+
1106
+ test("slots have hasNotification field", function()
1107
+ slots = {}
1108
+ slotCount = 2
1109
+ initSlots()
1110
+ assert(slots[1].hasNotification == false, "slot should have hasNotification = false")
1111
+ restore()
1112
+ end)
1113
+
1114
+ test("findSlotByWindowId returns nil for unknown window", function()
1115
+ assertEqual(findSlotByWindowId(999999999), nil)
1116
+ end)
1117
+
1118
+ test("findSlotByWindowId returns nil for nil input", function()
1119
+ assertEqual(findSlotByWindowId(nil), nil)
1120
+ end)
1121
+
1122
+ test("triggerNotification is a function", function()
1123
+ assert(type(triggerNotification) == "function", "triggerNotification should be a function")
1124
+ end)
1125
+
1126
+ test("clearNotification is a function", function()
1127
+ assert(type(clearNotification) == "function", "clearNotification should be a function")
1128
+ end)
1129
+
1130
+ test("triggerNotification returns false for invalid slot", function()
1131
+ assertEqual(triggerNotification(9999), false)
1132
+ end)
1133
+
1134
+ test("clearNotification returns false for invalid slot", function()
1135
+ assertEqual(clearNotification(9999), false)
1136
+ end)
1137
+
707
1138
  print("\n=== Results: " .. passed .. " passed, " .. failed .. " failed ===\n")
708
1139
  return failed == 0
709
1140
  end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dock",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "A lightweight terminal dock for macOS to manage Claude Code sessions",
5
5
  "bin": {
6
6
  "claude-dock": "bin/cli.js"