claude-dock 1.0.1 → 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 +395 -19
- 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
|
@@ -11,9 +11,10 @@ local config = {
|
|
|
11
11
|
margin = 10,
|
|
12
12
|
bottomOffset = 5,
|
|
13
13
|
addButtonWidth = 40,
|
|
14
|
+
utilityButtonWidth = 28,
|
|
14
15
|
initialSlots = 3,
|
|
15
16
|
elementsPerSlot = 4, -- bg, border, title, status
|
|
16
|
-
baseElements =
|
|
17
|
+
baseElements = 6, -- dock bg + border + help btn + help text + minimize btn + minimize text
|
|
17
18
|
windowCaptureDelay = 0.3,
|
|
18
19
|
windowCaptureRetries = 5,
|
|
19
20
|
colors = {
|
|
@@ -27,6 +28,10 @@ local config = {
|
|
|
27
28
|
textSecondary = { red = 0.5, green = 0.5, blue = 0.5, alpha = 1 },
|
|
28
29
|
addBtnBg = { red = 0.2, green = 0.25, blue = 0.2, alpha = 1 },
|
|
29
30
|
addBtnText = { red = 0.6, green = 0.8, blue = 0.6, alpha = 1 },
|
|
31
|
+
minBtnBg = { red = 0.18, green = 0.18, blue = 0.25, alpha = 1 },
|
|
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 },
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
|
|
@@ -35,6 +40,28 @@ local slotCount = config.initialSlots
|
|
|
35
40
|
local slots = {}
|
|
36
41
|
local dock = nil
|
|
37
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
|
|
38
65
|
|
|
39
66
|
-- Resource handles (for cleanup on reload)
|
|
40
67
|
local windowFilter = nil
|
|
@@ -63,6 +90,10 @@ local function cleanup()
|
|
|
63
90
|
tooltip:delete()
|
|
64
91
|
tooltip = nil
|
|
65
92
|
end
|
|
93
|
+
if helpPanel then
|
|
94
|
+
helpPanel:delete()
|
|
95
|
+
helpPanel = nil
|
|
96
|
+
end
|
|
66
97
|
end
|
|
67
98
|
cleanup() -- Clean up any previous instance
|
|
68
99
|
|
|
@@ -77,7 +108,10 @@ initSlots()
|
|
|
77
108
|
|
|
78
109
|
-- Calculate dock dimensions
|
|
79
110
|
local function getDockWidth()
|
|
80
|
-
|
|
111
|
+
-- Left: small utility button (minimize), Right: add button
|
|
112
|
+
local leftButtonWidth = config.utilityButtonWidth + config.gap
|
|
113
|
+
local rightButtonWidth = config.addButtonWidth
|
|
114
|
+
return leftButtonWidth + (config.slotWidth * slotCount) + (config.gap * (slotCount - 1)) + config.gap + rightButtonWidth + (config.margin * 2)
|
|
81
115
|
end
|
|
82
116
|
|
|
83
117
|
local function getDockHeight()
|
|
@@ -90,9 +124,14 @@ local function getDockFrame()
|
|
|
90
124
|
local frame = screen:fullFrame()
|
|
91
125
|
local dockWidth = getDockWidth()
|
|
92
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
|
|
93
132
|
return {
|
|
94
133
|
x = (frame.w - dockWidth) / 2,
|
|
95
|
-
y = frame.h - dockHeight -
|
|
134
|
+
y = frame.h - dockHeight - bottomOffset,
|
|
96
135
|
w = dockWidth,
|
|
97
136
|
h = dockHeight
|
|
98
137
|
}
|
|
@@ -114,18 +153,13 @@ local function getWindowTitle(win)
|
|
|
114
153
|
end
|
|
115
154
|
|
|
116
155
|
-- Tooltip helpers
|
|
117
|
-
local function
|
|
156
|
+
local function showTooltipAt(text, x, y)
|
|
118
157
|
if tooltip then tooltip:delete() end
|
|
119
|
-
local dockFrame = getDockFrame()
|
|
120
|
-
if not dockFrame then return end
|
|
121
158
|
|
|
122
159
|
local tipWidth = 50
|
|
123
160
|
local tipHeight = 24
|
|
124
|
-
local addBtnX = dockFrame.x + config.margin + (slotCount * (config.slotWidth + config.gap))
|
|
125
|
-
local tipX = addBtnX + (config.addButtonWidth - tipWidth) / 2
|
|
126
|
-
local tipY = dockFrame.y - tipHeight - 5
|
|
127
161
|
|
|
128
|
-
tooltip = hs.canvas.new({ x =
|
|
162
|
+
tooltip = hs.canvas.new({ x = x, y = y, w = tipWidth, h = tipHeight })
|
|
129
163
|
tooltip:appendElements({
|
|
130
164
|
type = "rectangle",
|
|
131
165
|
action = "fill",
|
|
@@ -146,6 +180,30 @@ local function showTooltip(text)
|
|
|
146
180
|
tooltip:show()
|
|
147
181
|
end
|
|
148
182
|
|
|
183
|
+
local function showButtonTooltip(text, buttonId)
|
|
184
|
+
local dockFrame = getDockFrame()
|
|
185
|
+
if not dockFrame then return end
|
|
186
|
+
|
|
187
|
+
local tipWidth = 50
|
|
188
|
+
local tipHeight = 24
|
|
189
|
+
local btnX, btnWidth
|
|
190
|
+
|
|
191
|
+
if buttonId == "minBtn" or buttonId == "helpBtn" then
|
|
192
|
+
-- Left side utility buttons
|
|
193
|
+
btnX = dockFrame.x + config.margin
|
|
194
|
+
btnWidth = config.utilityButtonWidth
|
|
195
|
+
elseif buttonId == "addBtn" then
|
|
196
|
+
-- Right side add button
|
|
197
|
+
btnX = dockFrame.x + config.margin + config.utilityButtonWidth + config.gap + (slotCount * config.slotWidth) + (slotCount * config.gap)
|
|
198
|
+
btnWidth = config.addButtonWidth
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
local tipX = btnX + (btnWidth - tipWidth) / 2
|
|
202
|
+
local tipY = dockFrame.y - tipHeight - 5
|
|
203
|
+
|
|
204
|
+
showTooltipAt(text, tipX, tipY)
|
|
205
|
+
end
|
|
206
|
+
|
|
149
207
|
local function hideTooltip()
|
|
150
208
|
if tooltip then
|
|
151
209
|
tooltip:delete()
|
|
@@ -153,9 +211,132 @@ local function hideTooltip()
|
|
|
153
211
|
end
|
|
154
212
|
end
|
|
155
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
|
+
|
|
156
336
|
-- Forward declarations
|
|
157
337
|
local createDock
|
|
158
338
|
local updateAllSlots
|
|
339
|
+
local minimizeAllTerminals
|
|
159
340
|
|
|
160
341
|
-- Update slot display
|
|
161
342
|
local function updateSlotDisplay(slotIndex)
|
|
@@ -348,9 +529,63 @@ createDock = function()
|
|
|
348
529
|
frame = { x = 0, y = 0, w = "100%", h = "100%" },
|
|
349
530
|
})
|
|
350
531
|
|
|
351
|
-
--
|
|
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
|
|
563
|
+
dock:appendElements({
|
|
564
|
+
type = "rectangle",
|
|
565
|
+
action = "fill",
|
|
566
|
+
frame = { x = utilBtnX, y = minBtnY, w = config.utilityButtonWidth, h = btnHeight },
|
|
567
|
+
roundedRectRadii = { xRadius = 6, yRadius = 6 },
|
|
568
|
+
fillColor = config.colors.minBtnBg,
|
|
569
|
+
trackMouseUp = true,
|
|
570
|
+
trackMouseEnterExit = true,
|
|
571
|
+
id = "minBtn",
|
|
572
|
+
})
|
|
573
|
+
dock:appendElements({
|
|
574
|
+
type = "text",
|
|
575
|
+
frame = { x = utilBtnX, y = minBtnY + 4, w = config.utilityButtonWidth, h = btnHeight },
|
|
576
|
+
text = "⌄",
|
|
577
|
+
textAlignment = "center",
|
|
578
|
+
textColor = config.colors.minBtnText,
|
|
579
|
+
textSize = 16,
|
|
580
|
+
textFont = ".AppleSystemUIFont",
|
|
581
|
+
trackMouseUp = true,
|
|
582
|
+
id = "minBtn",
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
-- Slots (offset by utility button)
|
|
586
|
+
local slotsStartX = config.margin + config.utilityButtonWidth + config.gap
|
|
352
587
|
for i = 1, slotCount do
|
|
353
|
-
local slotX =
|
|
588
|
+
local slotX = slotsStartX + ((i - 1) * (config.slotWidth + config.gap))
|
|
354
589
|
|
|
355
590
|
dock:appendElements({
|
|
356
591
|
type = "rectangle",
|
|
@@ -392,8 +627,8 @@ createDock = function()
|
|
|
392
627
|
})
|
|
393
628
|
end
|
|
394
629
|
|
|
395
|
-
-- Add button
|
|
396
|
-
local addBtnX =
|
|
630
|
+
-- Add button (right side)
|
|
631
|
+
local addBtnX = slotsStartX + (slotCount * (config.slotWidth + config.gap))
|
|
397
632
|
dock:appendElements({
|
|
398
633
|
type = "rectangle",
|
|
399
634
|
action = "fill",
|
|
@@ -412,12 +647,18 @@ createDock = function()
|
|
|
412
647
|
textColor = config.colors.addBtnText,
|
|
413
648
|
textSize = 28,
|
|
414
649
|
textFont = ".AppleSystemUIFont",
|
|
650
|
+
trackMouseUp = true,
|
|
651
|
+
id = "addBtn",
|
|
415
652
|
})
|
|
416
653
|
|
|
417
654
|
dock:mouseCallback(function(_, event, id)
|
|
418
655
|
if event == "mouseUp" then
|
|
419
656
|
if id == "addBtn" then
|
|
420
657
|
addSlot()
|
|
658
|
+
elseif id == "minBtn" then
|
|
659
|
+
minimizeAllTerminals()
|
|
660
|
+
elseif id == "helpBtn" then
|
|
661
|
+
showHelpPanel()
|
|
421
662
|
elseif id and id:match("^slot") then
|
|
422
663
|
local idx = tonumber(id:match("%d+"))
|
|
423
664
|
if idx then
|
|
@@ -425,9 +666,15 @@ createDock = function()
|
|
|
425
666
|
onSlotClick(idx, mods.alt)
|
|
426
667
|
end
|
|
427
668
|
end
|
|
428
|
-
elseif event == "mouseEnter"
|
|
429
|
-
|
|
430
|
-
|
|
669
|
+
elseif event == "mouseEnter" then
|
|
670
|
+
if id == "addBtn" then
|
|
671
|
+
showButtonTooltip("⌘⌥N", "addBtn")
|
|
672
|
+
elseif id == "minBtn" then
|
|
673
|
+
showButtonTooltip("⌘⌥M", "minBtn")
|
|
674
|
+
elseif id == "helpBtn" then
|
|
675
|
+
showButtonTooltip("Help", "helpBtn")
|
|
676
|
+
end
|
|
677
|
+
elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn" or id == "helpBtn") then
|
|
431
678
|
hideTooltip()
|
|
432
679
|
end
|
|
433
680
|
end)
|
|
@@ -444,6 +691,22 @@ local function toggleDock()
|
|
|
444
691
|
end
|
|
445
692
|
end
|
|
446
693
|
|
|
694
|
+
-- Minimize all terminal windows
|
|
695
|
+
minimizeAllTerminals = function()
|
|
696
|
+
local minimizedCount = 0
|
|
697
|
+
for _, slot in ipairs(slots) do
|
|
698
|
+
local win = getWindow(slot.windowId)
|
|
699
|
+
if win and not win:isMinimized() then
|
|
700
|
+
win:minimize()
|
|
701
|
+
minimizedCount = minimizedCount + 1
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
if minimizedCount > 0 then
|
|
705
|
+
hs.alert.show("Minimized " .. minimizedCount .. " terminal" .. (minimizedCount > 1 and "s" or ""))
|
|
706
|
+
end
|
|
707
|
+
updateAllSlots()
|
|
708
|
+
end
|
|
709
|
+
|
|
447
710
|
-- Window event watcher for immediate updates
|
|
448
711
|
windowFilter = hs.window.filter.new("Terminal")
|
|
449
712
|
windowFilter:subscribe({
|
|
@@ -471,15 +734,69 @@ screenWatcher = hs.screen.watcher.new(function()
|
|
|
471
734
|
end)
|
|
472
735
|
screenWatcher:start()
|
|
473
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
|
+
|
|
474
782
|
-- Initialize
|
|
783
|
+
local showRepositionedMsg = checkMacOSDockOnStartup()
|
|
475
784
|
createDock()
|
|
476
785
|
|
|
477
786
|
-- Hotkeys
|
|
478
787
|
hs.hotkey.bind({"cmd", "alt"}, "T", toggleDock)
|
|
479
788
|
hs.hotkey.bind({"cmd", "alt"}, "N", addSlot)
|
|
789
|
+
hs.hotkey.bind({"cmd", "alt"}, "M", minimizeAllTerminals)
|
|
480
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)
|
|
481
793
|
|
|
482
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
|
|
483
800
|
|
|
484
801
|
-- ===================
|
|
485
802
|
-- TESTS (run with: hs -c "runTests()")
|
|
@@ -487,7 +804,11 @@ hs.alert.show("Claude Dock Ready")
|
|
|
487
804
|
|
|
488
805
|
function runTests()
|
|
489
806
|
local passed, failed = 0, 0
|
|
490
|
-
local savedSlotCount
|
|
807
|
+
local savedSlotCount = slotCount
|
|
808
|
+
local savedSlots = {}
|
|
809
|
+
for i, s in ipairs(slots) do
|
|
810
|
+
savedSlots[i] = { windowId = s.windowId, customName = s.customName, pending = s.pending }
|
|
811
|
+
end
|
|
491
812
|
|
|
492
813
|
local function test(name, fn)
|
|
493
814
|
local ok, err = pcall(fn)
|
|
@@ -507,7 +828,11 @@ function runTests()
|
|
|
507
828
|
end
|
|
508
829
|
|
|
509
830
|
local function restore()
|
|
510
|
-
slotCount
|
|
831
|
+
slotCount = savedSlotCount
|
|
832
|
+
slots = {}
|
|
833
|
+
for i, s in ipairs(savedSlots) do
|
|
834
|
+
slots[i] = { windowId = s.windowId, customName = s.customName, pending = s.pending }
|
|
835
|
+
end
|
|
511
836
|
end
|
|
512
837
|
|
|
513
838
|
print("\n=== Claude Dock Tests ===\n")
|
|
@@ -598,6 +923,57 @@ function runTests()
|
|
|
598
923
|
assert(frame.x and frame.y and frame.w and frame.h, "frame should have x,y,w,h")
|
|
599
924
|
end)
|
|
600
925
|
|
|
926
|
+
test("minimizeAllTerminals is a function", function()
|
|
927
|
+
assert(type(minimizeAllTerminals) == "function", "minimizeAllTerminals should be a function")
|
|
928
|
+
end)
|
|
929
|
+
|
|
930
|
+
test("minimizeAllTerminals handles empty slots", function()
|
|
931
|
+
slots = {{ windowId = nil }, { windowId = nil }}
|
|
932
|
+
slotCount = 2
|
|
933
|
+
local ok = pcall(minimizeAllTerminals)
|
|
934
|
+
restore()
|
|
935
|
+
assert(ok, "should not error with empty slots")
|
|
936
|
+
end)
|
|
937
|
+
|
|
938
|
+
test("minimizeAllTerminals handles invalid windowIds", function()
|
|
939
|
+
slots = {{ windowId = 999999999 }, { windowId = 888888888 }}
|
|
940
|
+
slotCount = 2
|
|
941
|
+
local ok = pcall(minimizeAllTerminals)
|
|
942
|
+
restore()
|
|
943
|
+
assert(ok, "should not error with invalid windowIds")
|
|
944
|
+
end)
|
|
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
|
+
|
|
601
977
|
print("\n=== Results: " .. passed .. " passed, " .. failed .. " failed ===\n")
|
|
602
978
|
return failed == 0
|
|
603
979
|
end
|