claude-dock 1.0.2 → 1.1.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 +283 -13
  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
@@ -14,7 +14,7 @@ local config = {
14
14
  utilityButtonWidth = 28,
15
15
  initialSlots = 3,
16
16
  elementsPerSlot = 4, -- bg, border, title, status
17
- baseElements = 4, -- dock bg + border + minimize btn + minimize text
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,6 +30,8 @@ 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
+ 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 },
33
35
  }
34
36
  }
35
37
 
@@ -38,6 +40,28 @@ local slotCount = config.initialSlots
38
40
  local slots = {}
39
41
  local dock = nil
40
42
  local tooltip = nil
43
+ local helpPanel = nil
44
+ local macOSDockAtBottom = false
45
+
46
+ -- Check macOS dock position
47
+ local function getMacOSDockPosition()
48
+ local output, status = hs.execute("defaults read com.apple.dock orientation 2>/dev/null")
49
+ if status and output then
50
+ output = output:gsub("%s+", "")
51
+ if output == "left" or output == "right" then
52
+ return output
53
+ end
54
+ end
55
+ return "bottom" -- default
56
+ end
57
+
58
+ -- Get macOS dock size (tile size + magnification consideration)
59
+ local function getMacOSDockHeight()
60
+ local tileSize = hs.execute("defaults read com.apple.dock tilesize 2>/dev/null")
61
+ tileSize = tonumber(tileSize) or 48
62
+ -- Add padding for dock chrome and gaps
63
+ return tileSize + 20
64
+ end
41
65
 
42
66
  -- Resource handles (for cleanup on reload)
43
67
  local windowFilter = nil
@@ -66,6 +90,10 @@ local function cleanup()
66
90
  tooltip:delete()
67
91
  tooltip = nil
68
92
  end
93
+ if helpPanel then
94
+ helpPanel:delete()
95
+ helpPanel = nil
96
+ end
69
97
  end
70
98
  cleanup() -- Clean up any previous instance
71
99
 
@@ -96,9 +124,14 @@ local function getDockFrame()
96
124
  local frame = screen:fullFrame()
97
125
  local dockWidth = getDockWidth()
98
126
  local dockHeight = getDockHeight()
127
+ local bottomOffset = config.bottomOffset
128
+ -- Add extra offset if macOS dock is at bottom
129
+ if macOSDockAtBottom then
130
+ bottomOffset = bottomOffset + getMacOSDockHeight()
131
+ end
99
132
  return {
100
133
  x = (frame.w - dockWidth) / 2,
101
- y = frame.h - dockHeight - config.bottomOffset,
134
+ y = frame.h - dockHeight - bottomOffset,
102
135
  w = dockWidth,
103
136
  h = dockHeight
104
137
  }
@@ -155,11 +188,11 @@ local function showButtonTooltip(text, buttonId)
155
188
  local tipHeight = 24
156
189
  local btnX, btnWidth
157
190
 
158
- if buttonId == "minBtn" then
159
- -- Left side utility button
191
+ if buttonId == "minBtn" or buttonId == "helpBtn" then
192
+ -- Left side utility buttons
160
193
  btnX = dockFrame.x + config.margin
161
194
  btnWidth = config.utilityButtonWidth
162
- else
195
+ elseif buttonId == "addBtn" then
163
196
  -- Right side add button
164
197
  btnX = dockFrame.x + config.margin + config.utilityButtonWidth + config.gap + (slotCount * config.slotWidth) + (slotCount * config.gap)
165
198
  btnWidth = config.addButtonWidth
@@ -178,6 +211,128 @@ local function hideTooltip()
178
211
  end
179
212
  end
180
213
 
214
+ -- Help panel
215
+ local function hideHelpPanel()
216
+ if helpPanel then
217
+ helpPanel:delete()
218
+ helpPanel = nil
219
+ end
220
+ end
221
+
222
+ local function showHelpPanel()
223
+ if helpPanel then
224
+ hideHelpPanel()
225
+ return
226
+ end
227
+
228
+ local screen = hs.screen.mainScreen()
229
+ if not screen then return end
230
+ local screenFrame = screen:fullFrame()
231
+
232
+ local panelWidth = 280
233
+ local panelHeight = 260
234
+ local panelX = (screenFrame.w - panelWidth) / 2
235
+ local panelY = (screenFrame.h - panelHeight) / 2
236
+
237
+ helpPanel = hs.canvas.new({ x = panelX, y = panelY, w = panelWidth, h = panelHeight })
238
+
239
+ -- Background
240
+ helpPanel:appendElements({
241
+ type = "rectangle",
242
+ action = "fill",
243
+ roundedRectRadii = { xRadius = 12, yRadius = 12 },
244
+ fillColor = { red = 0.1, green = 0.1, blue = 0.1, alpha = 0.95 },
245
+ frame = { x = 0, y = 0, w = "100%", h = "100%" },
246
+ })
247
+
248
+ -- Border
249
+ helpPanel:appendElements({
250
+ type = "rectangle",
251
+ action = "stroke",
252
+ roundedRectRadii = { xRadius = 12, yRadius = 12 },
253
+ strokeColor = { red = 1, green = 1, blue = 1, alpha = 0.2 },
254
+ strokeWidth = 1,
255
+ frame = { x = 0, y = 0, w = "100%", h = "100%" },
256
+ })
257
+
258
+ -- Title
259
+ helpPanel:appendElements({
260
+ type = "text",
261
+ frame = { x = 0, y = 15, w = panelWidth, h = 24 },
262
+ text = "Claude Dock Shortcuts",
263
+ textAlignment = "center",
264
+ textColor = { red = 1, green = 1, blue = 1, alpha = 1 },
265
+ textSize = 16,
266
+ textFont = ".AppleSystemUIFontBold",
267
+ })
268
+
269
+ -- Shortcuts list
270
+ local shortcuts = {
271
+ { key = "⌘⌥T", desc = "Toggle dock" },
272
+ { key = "⌘⌥N", desc = "Add new terminal" },
273
+ { key = "⌘⌥M", desc = "Minimize all terminals" },
274
+ { key = "⌘⌥R", desc = "Reload config" },
275
+ { key = "⌘⌥L", desc = "Move macOS Dock left" },
276
+ { key = "⌘⌥B", desc = "Move macOS Dock bottom" },
277
+ { key = "⌥+Click", desc = "Rename slot" },
278
+ }
279
+
280
+ local startY = 50
281
+ for i, shortcut in ipairs(shortcuts) do
282
+ local y = startY + ((i - 1) * 24)
283
+ helpPanel:appendElements({
284
+ type = "text",
285
+ frame = { x = 20, y = y, w = 70, h = 20 },
286
+ text = shortcut.key,
287
+ textAlignment = "left",
288
+ textColor = { red = 0.6, green = 0.8, blue = 1, alpha = 1 },
289
+ textSize = 13,
290
+ textFont = ".AppleSystemUIFont",
291
+ })
292
+ helpPanel:appendElements({
293
+ type = "text",
294
+ frame = { x = 95, y = y, w = 170, h = 20 },
295
+ text = shortcut.desc,
296
+ textAlignment = "left",
297
+ textColor = { red = 0.8, green = 0.8, blue = 0.8, alpha = 1 },
298
+ textSize = 13,
299
+ textFont = ".AppleSystemUIFont",
300
+ })
301
+ end
302
+
303
+ -- Close button
304
+ local closeBtnY = panelHeight - 45
305
+ helpPanel:appendElements({
306
+ type = "rectangle",
307
+ action = "fill",
308
+ frame = { x = (panelWidth - 80) / 2, y = closeBtnY, w = 80, h = 30 },
309
+ roundedRectRadii = { xRadius = 6, yRadius = 6 },
310
+ fillColor = { red = 0.25, green = 0.25, blue = 0.25, alpha = 1 },
311
+ trackMouseUp = true,
312
+ id = "closeBtn",
313
+ })
314
+ helpPanel:appendElements({
315
+ type = "text",
316
+ frame = { x = (panelWidth - 80) / 2, y = closeBtnY + 6, w = 80, h = 20 },
317
+ text = "Close",
318
+ textAlignment = "center",
319
+ textColor = { red = 0.9, green = 0.9, blue = 0.9, alpha = 1 },
320
+ textSize = 13,
321
+ textFont = ".AppleSystemUIFont",
322
+ trackMouseUp = true,
323
+ id = "closeBtn",
324
+ })
325
+
326
+ helpPanel:mouseCallback(function(_, event, id)
327
+ if event == "mouseUp" and id == "closeBtn" then
328
+ hideHelpPanel()
329
+ end
330
+ end)
331
+
332
+ helpPanel:level(hs.canvas.windowLevels.modalPanel)
333
+ helpPanel:show()
334
+ end
335
+
181
336
  -- Forward declarations
182
337
  local createDock
183
338
  local updateAllSlots
@@ -374,14 +529,41 @@ createDock = function()
374
529
  frame = { x = 0, y = 0, w = "100%", h = "100%" },
375
530
  })
376
531
 
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
532
+ -- Utility buttons (left side, stacked: help on top, minimize on bottom)
533
+ local utilBtnX = config.margin
534
+ local btnGap = 4
535
+ local btnHeight = (config.slotHeight - btnGap) / 2
536
+
537
+ -- Help button (top)
538
+ local helpBtnY = config.margin
539
+ dock:appendElements({
540
+ type = "rectangle",
541
+ action = "fill",
542
+ frame = { x = utilBtnX, y = helpBtnY, w = config.utilityButtonWidth, h = btnHeight },
543
+ roundedRectRadii = { xRadius = 6, yRadius = 6 },
544
+ fillColor = config.colors.helpBtnBg,
545
+ trackMouseUp = true,
546
+ trackMouseEnterExit = true,
547
+ id = "helpBtn",
548
+ })
549
+ dock:appendElements({
550
+ type = "text",
551
+ frame = { x = utilBtnX, y = helpBtnY + 4, w = config.utilityButtonWidth, h = btnHeight },
552
+ text = "?",
553
+ textAlignment = "center",
554
+ textColor = config.colors.helpBtnText,
555
+ textSize = 16,
556
+ textFont = ".AppleSystemUIFontBold",
557
+ trackMouseUp = true,
558
+ id = "helpBtn",
559
+ })
560
+
561
+ -- Minimize button (bottom)
562
+ local minBtnY = config.margin + btnHeight + btnGap
381
563
  dock:appendElements({
382
564
  type = "rectangle",
383
565
  action = "fill",
384
- frame = { x = minBtnX, y = minBtnY, w = config.utilityButtonWidth, h = minBtnHeight },
566
+ frame = { x = utilBtnX, y = minBtnY, w = config.utilityButtonWidth, h = btnHeight },
385
567
  roundedRectRadii = { xRadius = 6, yRadius = 6 },
386
568
  fillColor = config.colors.minBtnBg,
387
569
  trackMouseUp = true,
@@ -390,11 +572,11 @@ createDock = function()
390
572
  })
391
573
  dock:appendElements({
392
574
  type = "text",
393
- frame = { x = minBtnX, y = minBtnY + 2, w = config.utilityButtonWidth, h = minBtnHeight },
575
+ frame = { x = utilBtnX, y = minBtnY + 4, w = config.utilityButtonWidth, h = btnHeight },
394
576
  text = "⌄",
395
577
  textAlignment = "center",
396
578
  textColor = config.colors.minBtnText,
397
- textSize = 18,
579
+ textSize = 16,
398
580
  textFont = ".AppleSystemUIFont",
399
581
  trackMouseUp = true,
400
582
  id = "minBtn",
@@ -475,6 +657,8 @@ createDock = function()
475
657
  addSlot()
476
658
  elseif id == "minBtn" then
477
659
  minimizeAllTerminals()
660
+ elseif id == "helpBtn" then
661
+ showHelpPanel()
478
662
  elseif id and id:match("^slot") then
479
663
  local idx = tonumber(id:match("%d+"))
480
664
  if idx then
@@ -487,8 +671,10 @@ createDock = function()
487
671
  showButtonTooltip("⌘⌥N", "addBtn")
488
672
  elseif id == "minBtn" then
489
673
  showButtonTooltip("⌘⌥M", "minBtn")
674
+ elseif id == "helpBtn" then
675
+ showButtonTooltip("Help", "helpBtn")
490
676
  end
491
- elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn") then
677
+ elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn" or id == "helpBtn") then
492
678
  hideTooltip()
493
679
  end
494
680
  end)
@@ -548,7 +734,53 @@ screenWatcher = hs.screen.watcher.new(function()
548
734
  end)
549
735
  screenWatcher:start()
550
736
 
737
+ -- Move macOS dock position
738
+ local function moveMacOSDockLeft()
739
+ hs.execute("defaults write com.apple.dock orientation left && killall Dock")
740
+ macOSDockAtBottom = false
741
+ hs.alert.show("macOS Dock moved to left")
742
+ -- Reposition Claude Dock
743
+ if dock then
744
+ local frame = getDockFrame()
745
+ if frame then dock:frame(frame) end
746
+ end
747
+ end
748
+
749
+ local function moveMacOSDockBottom()
750
+ hs.execute("defaults write com.apple.dock orientation bottom && killall Dock")
751
+ macOSDockAtBottom = true
752
+ hs.alert.show("macOS Dock moved to bottom")
753
+ -- Reposition Claude Dock
754
+ if dock then
755
+ local frame = getDockFrame()
756
+ if frame then dock:frame(frame) end
757
+ end
758
+ end
759
+
760
+ -- Check macOS dock and prompt user if at bottom
761
+ -- Returns true if user chose to keep dock at bottom
762
+ local function checkMacOSDockOnStartup()
763
+ local position = getMacOSDockPosition()
764
+ if position == "bottom" then
765
+ local button = hs.dialog.blockAlert(
766
+ "macOS Dock Position",
767
+ "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?",
768
+ "Move to Left",
769
+ "Keep at Bottom"
770
+ )
771
+ if button == "Move to Left" then
772
+ moveMacOSDockLeft()
773
+ return false
774
+ else
775
+ macOSDockAtBottom = true
776
+ return true
777
+ end
778
+ end
779
+ return false
780
+ end
781
+
551
782
  -- Initialize
783
+ local showRepositionedMsg = checkMacOSDockOnStartup()
552
784
  createDock()
553
785
 
554
786
  -- Hotkeys
@@ -556,8 +788,15 @@ hs.hotkey.bind({"cmd", "alt"}, "T", toggleDock)
556
788
  hs.hotkey.bind({"cmd", "alt"}, "N", addSlot)
557
789
  hs.hotkey.bind({"cmd", "alt"}, "M", minimizeAllTerminals)
558
790
  hs.hotkey.bind({"cmd", "alt"}, "R", hs.reload)
791
+ hs.hotkey.bind({"cmd", "alt"}, "L", moveMacOSDockLeft)
792
+ hs.hotkey.bind({"cmd", "alt"}, "B", moveMacOSDockBottom)
559
793
 
560
794
  hs.alert.show("Claude Dock Ready")
795
+ if showRepositionedMsg then
796
+ hs.timer.doAfter(1.5, function()
797
+ hs.alert.show("Positioned above macOS Dock")
798
+ end)
799
+ end
561
800
 
562
801
  -- ===================
563
802
  -- TESTS (run with: hs -c "runTests()")
@@ -704,6 +943,37 @@ function runTests()
704
943
  assert(ok, "should not error with invalid windowIds")
705
944
  end)
706
945
 
946
+ test("moveMacOSDockLeft is a function", function()
947
+ assert(type(moveMacOSDockLeft) == "function", "moveMacOSDockLeft should be a function")
948
+ end)
949
+
950
+ test("moveMacOSDockBottom is a function", function()
951
+ assert(type(moveMacOSDockBottom) == "function", "moveMacOSDockBottom should be a function")
952
+ end)
953
+
954
+ test("getMacOSDockPosition returns valid position", function()
955
+ local pos = getMacOSDockPosition()
956
+ assert(pos == "left" or pos == "right" or pos == "bottom", "position should be left, right, or bottom")
957
+ end)
958
+
959
+ test("getMacOSDockHeight returns number", function()
960
+ local height = getMacOSDockHeight()
961
+ assert(type(height) == "number", "height should be a number")
962
+ assert(height > 0, "height should be positive")
963
+ end)
964
+
965
+ test("checkMacOSDockOnStartup is a function", function()
966
+ assert(type(checkMacOSDockOnStartup) == "function", "checkMacOSDockOnStartup should be a function")
967
+ end)
968
+
969
+ test("showHelpPanel is a function", function()
970
+ assert(type(showHelpPanel) == "function", "showHelpPanel should be a function")
971
+ end)
972
+
973
+ test("hideHelpPanel is a function", function()
974
+ assert(type(hideHelpPanel) == "function", "hideHelpPanel should be a function")
975
+ end)
976
+
707
977
  print("\n=== Results: " .. passed .. " passed, " .. failed .. " failed ===\n")
708
978
  return failed == 0
709
979
  end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dock",
3
- "version": "1.0.2",
3
+ "version": "1.1.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"