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.
- package/bin/cli.js +5 -0
- package/init.lua +283 -13
- 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 =
|
|
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 -
|
|
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
|
|
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
|
-
|
|
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
|
-
--
|
|
378
|
-
local
|
|
379
|
-
local
|
|
380
|
-
local
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|