claude-dock 1.0.0 → 1.0.2
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/README.md +22 -7
- package/init.lua +124 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A lightweight, expandable terminal dock for macOS built with [Hammerspoon](https://www.hammerspoon.org/). Designed for managing multiple Claude Code terminal sessions.
|
|
4
4
|
|
|
5
5
|

|
|
6
|
+
[](https://www.npmjs.com/package/claude-dock)
|
|
6
7
|
|
|
7
8
|
## Features
|
|
8
9
|
|
|
@@ -16,7 +17,22 @@ A lightweight, expandable terminal dock for macOS built with [Hammerspoon](https
|
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npx claude-dock
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That's it! The installer will:
|
|
25
|
+
- Install Hammerspoon (if needed)
|
|
26
|
+
- Set up the dock configuration
|
|
27
|
+
- Launch Hammerspoon
|
|
28
|
+
|
|
29
|
+
**Note:** You'll need to grant Accessibility permissions when prompted:
|
|
30
|
+
System Settings → Privacy & Security → Accessibility → Enable Hammerspoon
|
|
31
|
+
|
|
32
|
+
### Manual Installation
|
|
33
|
+
|
|
34
|
+
<details>
|
|
35
|
+
<summary>Click to expand manual setup instructions</summary>
|
|
20
36
|
|
|
21
37
|
1. Install [Hammerspoon](https://www.hammerspoon.org/):
|
|
22
38
|
```bash
|
|
@@ -27,16 +43,15 @@ A lightweight, expandable terminal dock for macOS built with [Hammerspoon](https
|
|
|
27
43
|
- System Settings > Privacy & Security > Accessibility
|
|
28
44
|
- Enable Hammerspoon
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
1. Clone this repo (or download the ZIP from GitHub)
|
|
33
|
-
|
|
34
|
-
2. Copy to Hammerspoon config:
|
|
46
|
+
3. Clone this repo and copy the config:
|
|
35
47
|
```bash
|
|
48
|
+
git clone https://github.com/matthewmolinar/claude-dock.git
|
|
36
49
|
cp claude-dock/init.lua ~/.hammerspoon/init.lua
|
|
37
50
|
```
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
4. Launch Hammerspoon (or reload if already running)
|
|
53
|
+
|
|
54
|
+
</details>
|
|
40
55
|
|
|
41
56
|
## Usage
|
|
42
57
|
|
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 = 4, -- dock bg + border + minimize btn + minimize text
|
|
17
18
|
windowCaptureDelay = 0.3,
|
|
18
19
|
windowCaptureRetries = 5,
|
|
19
20
|
colors = {
|
|
@@ -27,6 +28,8 @@ 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 },
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
@@ -77,7 +80,10 @@ initSlots()
|
|
|
77
80
|
|
|
78
81
|
-- Calculate dock dimensions
|
|
79
82
|
local function getDockWidth()
|
|
80
|
-
|
|
83
|
+
-- Left: small utility button (minimize), Right: add button
|
|
84
|
+
local leftButtonWidth = config.utilityButtonWidth + config.gap
|
|
85
|
+
local rightButtonWidth = config.addButtonWidth
|
|
86
|
+
return leftButtonWidth + (config.slotWidth * slotCount) + (config.gap * (slotCount - 1)) + config.gap + rightButtonWidth + (config.margin * 2)
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
local function getDockHeight()
|
|
@@ -114,18 +120,13 @@ local function getWindowTitle(win)
|
|
|
114
120
|
end
|
|
115
121
|
|
|
116
122
|
-- Tooltip helpers
|
|
117
|
-
local function
|
|
123
|
+
local function showTooltipAt(text, x, y)
|
|
118
124
|
if tooltip then tooltip:delete() end
|
|
119
|
-
local dockFrame = getDockFrame()
|
|
120
|
-
if not dockFrame then return end
|
|
121
125
|
|
|
122
126
|
local tipWidth = 50
|
|
123
127
|
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
128
|
|
|
128
|
-
tooltip = hs.canvas.new({ x =
|
|
129
|
+
tooltip = hs.canvas.new({ x = x, y = y, w = tipWidth, h = tipHeight })
|
|
129
130
|
tooltip:appendElements({
|
|
130
131
|
type = "rectangle",
|
|
131
132
|
action = "fill",
|
|
@@ -146,6 +147,30 @@ local function showTooltip(text)
|
|
|
146
147
|
tooltip:show()
|
|
147
148
|
end
|
|
148
149
|
|
|
150
|
+
local function showButtonTooltip(text, buttonId)
|
|
151
|
+
local dockFrame = getDockFrame()
|
|
152
|
+
if not dockFrame then return end
|
|
153
|
+
|
|
154
|
+
local tipWidth = 50
|
|
155
|
+
local tipHeight = 24
|
|
156
|
+
local btnX, btnWidth
|
|
157
|
+
|
|
158
|
+
if buttonId == "minBtn" then
|
|
159
|
+
-- Left side utility button
|
|
160
|
+
btnX = dockFrame.x + config.margin
|
|
161
|
+
btnWidth = config.utilityButtonWidth
|
|
162
|
+
else
|
|
163
|
+
-- Right side add button
|
|
164
|
+
btnX = dockFrame.x + config.margin + config.utilityButtonWidth + config.gap + (slotCount * config.slotWidth) + (slotCount * config.gap)
|
|
165
|
+
btnWidth = config.addButtonWidth
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
local tipX = btnX + (btnWidth - tipWidth) / 2
|
|
169
|
+
local tipY = dockFrame.y - tipHeight - 5
|
|
170
|
+
|
|
171
|
+
showTooltipAt(text, tipX, tipY)
|
|
172
|
+
end
|
|
173
|
+
|
|
149
174
|
local function hideTooltip()
|
|
150
175
|
if tooltip then
|
|
151
176
|
tooltip:delete()
|
|
@@ -156,6 +181,7 @@ end
|
|
|
156
181
|
-- Forward declarations
|
|
157
182
|
local createDock
|
|
158
183
|
local updateAllSlots
|
|
184
|
+
local minimizeAllTerminals
|
|
159
185
|
|
|
160
186
|
-- Update slot display
|
|
161
187
|
local function updateSlotDisplay(slotIndex)
|
|
@@ -348,9 +374,36 @@ createDock = function()
|
|
|
348
374
|
frame = { x = 0, y = 0, w = "100%", h = "100%" },
|
|
349
375
|
})
|
|
350
376
|
|
|
351
|
-
--
|
|
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
|
|
381
|
+
dock:appendElements({
|
|
382
|
+
type = "rectangle",
|
|
383
|
+
action = "fill",
|
|
384
|
+
frame = { x = minBtnX, y = minBtnY, w = config.utilityButtonWidth, h = minBtnHeight },
|
|
385
|
+
roundedRectRadii = { xRadius = 6, yRadius = 6 },
|
|
386
|
+
fillColor = config.colors.minBtnBg,
|
|
387
|
+
trackMouseUp = true,
|
|
388
|
+
trackMouseEnterExit = true,
|
|
389
|
+
id = "minBtn",
|
|
390
|
+
})
|
|
391
|
+
dock:appendElements({
|
|
392
|
+
type = "text",
|
|
393
|
+
frame = { x = minBtnX, y = minBtnY + 2, w = config.utilityButtonWidth, h = minBtnHeight },
|
|
394
|
+
text = "⌄",
|
|
395
|
+
textAlignment = "center",
|
|
396
|
+
textColor = config.colors.minBtnText,
|
|
397
|
+
textSize = 18,
|
|
398
|
+
textFont = ".AppleSystemUIFont",
|
|
399
|
+
trackMouseUp = true,
|
|
400
|
+
id = "minBtn",
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
-- Slots (offset by utility button)
|
|
404
|
+
local slotsStartX = config.margin + config.utilityButtonWidth + config.gap
|
|
352
405
|
for i = 1, slotCount do
|
|
353
|
-
local slotX =
|
|
406
|
+
local slotX = slotsStartX + ((i - 1) * (config.slotWidth + config.gap))
|
|
354
407
|
|
|
355
408
|
dock:appendElements({
|
|
356
409
|
type = "rectangle",
|
|
@@ -392,8 +445,8 @@ createDock = function()
|
|
|
392
445
|
})
|
|
393
446
|
end
|
|
394
447
|
|
|
395
|
-
-- Add button
|
|
396
|
-
local addBtnX =
|
|
448
|
+
-- Add button (right side)
|
|
449
|
+
local addBtnX = slotsStartX + (slotCount * (config.slotWidth + config.gap))
|
|
397
450
|
dock:appendElements({
|
|
398
451
|
type = "rectangle",
|
|
399
452
|
action = "fill",
|
|
@@ -412,12 +465,16 @@ createDock = function()
|
|
|
412
465
|
textColor = config.colors.addBtnText,
|
|
413
466
|
textSize = 28,
|
|
414
467
|
textFont = ".AppleSystemUIFont",
|
|
468
|
+
trackMouseUp = true,
|
|
469
|
+
id = "addBtn",
|
|
415
470
|
})
|
|
416
471
|
|
|
417
472
|
dock:mouseCallback(function(_, event, id)
|
|
418
473
|
if event == "mouseUp" then
|
|
419
474
|
if id == "addBtn" then
|
|
420
475
|
addSlot()
|
|
476
|
+
elseif id == "minBtn" then
|
|
477
|
+
minimizeAllTerminals()
|
|
421
478
|
elseif id and id:match("^slot") then
|
|
422
479
|
local idx = tonumber(id:match("%d+"))
|
|
423
480
|
if idx then
|
|
@@ -425,9 +482,13 @@ createDock = function()
|
|
|
425
482
|
onSlotClick(idx, mods.alt)
|
|
426
483
|
end
|
|
427
484
|
end
|
|
428
|
-
elseif event == "mouseEnter"
|
|
429
|
-
|
|
430
|
-
|
|
485
|
+
elseif event == "mouseEnter" then
|
|
486
|
+
if id == "addBtn" then
|
|
487
|
+
showButtonTooltip("⌘⌥N", "addBtn")
|
|
488
|
+
elseif id == "minBtn" then
|
|
489
|
+
showButtonTooltip("⌘⌥M", "minBtn")
|
|
490
|
+
end
|
|
491
|
+
elseif event == "mouseExit" and (id == "addBtn" or id == "minBtn") then
|
|
431
492
|
hideTooltip()
|
|
432
493
|
end
|
|
433
494
|
end)
|
|
@@ -444,6 +505,22 @@ local function toggleDock()
|
|
|
444
505
|
end
|
|
445
506
|
end
|
|
446
507
|
|
|
508
|
+
-- Minimize all terminal windows
|
|
509
|
+
minimizeAllTerminals = function()
|
|
510
|
+
local minimizedCount = 0
|
|
511
|
+
for _, slot in ipairs(slots) do
|
|
512
|
+
local win = getWindow(slot.windowId)
|
|
513
|
+
if win and not win:isMinimized() then
|
|
514
|
+
win:minimize()
|
|
515
|
+
minimizedCount = minimizedCount + 1
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
if minimizedCount > 0 then
|
|
519
|
+
hs.alert.show("Minimized " .. minimizedCount .. " terminal" .. (minimizedCount > 1 and "s" or ""))
|
|
520
|
+
end
|
|
521
|
+
updateAllSlots()
|
|
522
|
+
end
|
|
523
|
+
|
|
447
524
|
-- Window event watcher for immediate updates
|
|
448
525
|
windowFilter = hs.window.filter.new("Terminal")
|
|
449
526
|
windowFilter:subscribe({
|
|
@@ -477,6 +554,7 @@ createDock()
|
|
|
477
554
|
-- Hotkeys
|
|
478
555
|
hs.hotkey.bind({"cmd", "alt"}, "T", toggleDock)
|
|
479
556
|
hs.hotkey.bind({"cmd", "alt"}, "N", addSlot)
|
|
557
|
+
hs.hotkey.bind({"cmd", "alt"}, "M", minimizeAllTerminals)
|
|
480
558
|
hs.hotkey.bind({"cmd", "alt"}, "R", hs.reload)
|
|
481
559
|
|
|
482
560
|
hs.alert.show("Claude Dock Ready")
|
|
@@ -487,7 +565,11 @@ hs.alert.show("Claude Dock Ready")
|
|
|
487
565
|
|
|
488
566
|
function runTests()
|
|
489
567
|
local passed, failed = 0, 0
|
|
490
|
-
local savedSlotCount
|
|
568
|
+
local savedSlotCount = slotCount
|
|
569
|
+
local savedSlots = {}
|
|
570
|
+
for i, s in ipairs(slots) do
|
|
571
|
+
savedSlots[i] = { windowId = s.windowId, customName = s.customName, pending = s.pending }
|
|
572
|
+
end
|
|
491
573
|
|
|
492
574
|
local function test(name, fn)
|
|
493
575
|
local ok, err = pcall(fn)
|
|
@@ -507,7 +589,11 @@ function runTests()
|
|
|
507
589
|
end
|
|
508
590
|
|
|
509
591
|
local function restore()
|
|
510
|
-
slotCount
|
|
592
|
+
slotCount = savedSlotCount
|
|
593
|
+
slots = {}
|
|
594
|
+
for i, s in ipairs(savedSlots) do
|
|
595
|
+
slots[i] = { windowId = s.windowId, customName = s.customName, pending = s.pending }
|
|
596
|
+
end
|
|
511
597
|
end
|
|
512
598
|
|
|
513
599
|
print("\n=== Claude Dock Tests ===\n")
|
|
@@ -598,6 +684,26 @@ function runTests()
|
|
|
598
684
|
assert(frame.x and frame.y and frame.w and frame.h, "frame should have x,y,w,h")
|
|
599
685
|
end)
|
|
600
686
|
|
|
687
|
+
test("minimizeAllTerminals is a function", function()
|
|
688
|
+
assert(type(minimizeAllTerminals) == "function", "minimizeAllTerminals should be a function")
|
|
689
|
+
end)
|
|
690
|
+
|
|
691
|
+
test("minimizeAllTerminals handles empty slots", function()
|
|
692
|
+
slots = {{ windowId = nil }, { windowId = nil }}
|
|
693
|
+
slotCount = 2
|
|
694
|
+
local ok = pcall(minimizeAllTerminals)
|
|
695
|
+
restore()
|
|
696
|
+
assert(ok, "should not error with empty slots")
|
|
697
|
+
end)
|
|
698
|
+
|
|
699
|
+
test("minimizeAllTerminals handles invalid windowIds", function()
|
|
700
|
+
slots = {{ windowId = 999999999 }, { windowId = 888888888 }}
|
|
701
|
+
slotCount = 2
|
|
702
|
+
local ok = pcall(minimizeAllTerminals)
|
|
703
|
+
restore()
|
|
704
|
+
assert(ok, "should not error with invalid windowIds")
|
|
705
|
+
end)
|
|
706
|
+
|
|
601
707
|
print("\n=== Results: " .. passed .. " passed, " .. failed .. " failed ===\n")
|
|
602
708
|
return failed == 0
|
|
603
709
|
end
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dock",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A lightweight terminal dock for macOS to manage Claude Code sessions",
|
|
5
5
|
"bin": {
|
|
6
|
-
"claude-dock": "
|
|
6
|
+
"claude-dock": "bin/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"keywords": [
|
|
9
9
|
"claude",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/matthewmolinar/claude-dock.git"
|
|
20
|
+
"url": "git+https://github.com/matthewmolinar/claude-dock.git"
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/matthewmolinar/claude-dock",
|
|
23
23
|
"engines": {
|