@mauribadnights/clooks 0.3.2 → 0.4.1
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 +276 -201
- package/agents/clooks.md +146 -0
- package/dist/agent.d.ts +9 -0
- package/dist/agent.js +43 -0
- package/dist/cli.js +126 -17
- package/dist/doctor.js +20 -0
- package/dist/handlers.d.ts +7 -1
- package/dist/handlers.js +105 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +10 -2
- package/dist/manifest.js +33 -0
- package/dist/server.d.ts +21 -1
- package/dist/server.js +126 -8
- package/dist/service.d.ts +27 -0
- package/dist/service.js +242 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +730 -0
- package/dist/types.d.ts +11 -0
- package/docs/DASHBOARD-VISION.md +66 -0
- package/docs/DEFERRED-FIXES.md +35 -0
- package/docs/architecture.png +0 -0
- package/docs/generate-diagram.py +177 -0
- package/package.json +3 -1
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface HookInput {
|
|
|
11
11
|
tool_name?: string;
|
|
12
12
|
tool_input?: Record<string, unknown>;
|
|
13
13
|
source?: string;
|
|
14
|
+
agent_type?: string;
|
|
14
15
|
stop_hook_active?: boolean;
|
|
15
16
|
[key: string]: unknown;
|
|
16
17
|
}
|
|
@@ -32,6 +33,9 @@ export interface LLMHandlerConfig {
|
|
|
32
33
|
enabled?: boolean;
|
|
33
34
|
sessionIsolation?: boolean;
|
|
34
35
|
depends?: string[];
|
|
36
|
+
async?: boolean;
|
|
37
|
+
agent?: string;
|
|
38
|
+
project?: string;
|
|
35
39
|
}
|
|
36
40
|
/** Script handler config */
|
|
37
41
|
export interface ScriptHandlerConfig {
|
|
@@ -43,6 +47,9 @@ export interface ScriptHandlerConfig {
|
|
|
43
47
|
enabled?: boolean;
|
|
44
48
|
sessionIsolation?: boolean;
|
|
45
49
|
depends?: string[];
|
|
50
|
+
async?: boolean;
|
|
51
|
+
agent?: string;
|
|
52
|
+
project?: string;
|
|
46
53
|
}
|
|
47
54
|
/** Inline handler config */
|
|
48
55
|
export interface InlineHandlerConfig {
|
|
@@ -54,6 +61,9 @@ export interface InlineHandlerConfig {
|
|
|
54
61
|
enabled?: boolean;
|
|
55
62
|
sessionIsolation?: boolean;
|
|
56
63
|
depends?: string[];
|
|
64
|
+
async?: boolean;
|
|
65
|
+
agent?: string;
|
|
66
|
+
project?: string;
|
|
57
67
|
}
|
|
58
68
|
/** Union of all handler configs */
|
|
59
69
|
export type HandlerConfig = ScriptHandlerConfig | InlineHandlerConfig | LLMHandlerConfig;
|
|
@@ -103,6 +113,7 @@ export interface MetricEntry {
|
|
|
103
113
|
usage?: TokenUsage;
|
|
104
114
|
cost_usd?: number;
|
|
105
115
|
session_id?: string;
|
|
116
|
+
agent_type?: string;
|
|
106
117
|
}
|
|
107
118
|
/** Extended handler result with cost info */
|
|
108
119
|
export interface HandlerResult {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Dashboard Vision — Visual Hook Manager
|
|
2
|
+
|
|
3
|
+
> **Status:** Future feature idea — NOT to be implemented now.
|
|
4
|
+
> **Target version:** v0.4 or later, after the plugin ecosystem (v0.3) is in place.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A web-based dashboard (accessible at `localhost:7890/dashboard` or similar) that provides a graphical interface for creating, configuring, and monitoring hooks — no YAML editing required.
|
|
9
|
+
|
|
10
|
+
## Hook Builder UI
|
|
11
|
+
|
|
12
|
+
The core feature: a visual hook creator.
|
|
13
|
+
|
|
14
|
+
### Event Selection
|
|
15
|
+
|
|
16
|
+
- Dropdown to select the hook event: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`, etc.
|
|
17
|
+
- Visual explanation of when each event fires and how often
|
|
18
|
+
|
|
19
|
+
### Handler Type
|
|
20
|
+
|
|
21
|
+
- Dropdown: **Script**, **Inline**, **LLM** (v0.2+)
|
|
22
|
+
- Each type shows its relevant configuration fields dynamically
|
|
23
|
+
|
|
24
|
+
### Configuration
|
|
25
|
+
|
|
26
|
+
- **Script handlers**: text field for the command, timeout slider, enable/disable toggle
|
|
27
|
+
- **LLM handlers**: model selector dropdown (Haiku/Sonnet/Opus), prompt textarea with syntax highlighting, temperature slider, max tokens input, batch group selector
|
|
28
|
+
- **Inline handlers**: file picker for the JS module
|
|
29
|
+
|
|
30
|
+
### Pre-built Actions (Quick Hooks)
|
|
31
|
+
|
|
32
|
+
Predefined hook templates users can enable with one click:
|
|
33
|
+
|
|
34
|
+
| Quick Hook | Description |
|
|
35
|
+
|---|---|
|
|
36
|
+
| Auto-commit on Stop | Commits staged changes when Claude stops |
|
|
37
|
+
| Dangerous command guard | Blocks `rm -rf`, `git push --force` on PreToolUse |
|
|
38
|
+
| Session logger | Records session start/stop times |
|
|
39
|
+
| Cost tracker | Monitors LLM usage across hooks |
|
|
40
|
+
| Context warning | Alerts when context window is running low |
|
|
41
|
+
| Custom | Blank template to build your own |
|
|
42
|
+
|
|
43
|
+
### Filter Configuration
|
|
44
|
+
|
|
45
|
+
- Visual filter builder: keyword chips, include/exclude toggle
|
|
46
|
+
- Preview: shows sample inputs and whether the filter would match
|
|
47
|
+
|
|
48
|
+
## Monitoring Dashboard
|
|
49
|
+
|
|
50
|
+
- Real-time view of hook fires, handler execution times, errors
|
|
51
|
+
- Per-handler graphs (fires over time, latency distribution)
|
|
52
|
+
- LLM cost tracker with daily/weekly charts
|
|
53
|
+
- Health status of all handlers (green/yellow/red)
|
|
54
|
+
|
|
55
|
+
## Manifest Sync
|
|
56
|
+
|
|
57
|
+
- Dashboard reads and writes `~/.clooks/manifest.yaml`
|
|
58
|
+
- Changes are live-reloaded by the daemon (no restart needed)
|
|
59
|
+
- Shows diff of pending changes before applying
|
|
60
|
+
- Version history of manifest changes
|
|
61
|
+
|
|
62
|
+
## Technology
|
|
63
|
+
|
|
64
|
+
- Served by the clooks daemon itself (add routes to existing HTTP server)
|
|
65
|
+
- Frontend: lightweight — could be vanilla HTML/CSS/JS or a small framework like Preact
|
|
66
|
+
- No build step for the dashboard — serve static files from a `dashboard/` directory
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Deferred Fixes (Historical)
|
|
2
|
+
|
|
3
|
+
> **All issues in this document were resolved in v0.3.0. This file is kept for historical reference.**
|
|
4
|
+
|
|
5
|
+
These issues were identified in the v0.2.1 audit and deferred to v0.3.0.
|
|
6
|
+
|
|
7
|
+
## Session Isolation + LLM Batching Violation -- RESOLVED
|
|
8
|
+
|
|
9
|
+
**Problem:** Two concurrent sessions with handlers sharing a `batchGroup` would batch together into one API call, violating session isolation guarantees.
|
|
10
|
+
|
|
11
|
+
**Resolution:** Batch groups are now scoped to `{batchGroup}:{session_id}`. Session ID is passed into `executeLLMHandlersBatched()` and plugins can declare batch groups as session-scoped or global.
|
|
12
|
+
|
|
13
|
+
## Auth Token Rotation -- RESOLVED
|
|
14
|
+
|
|
15
|
+
**Problem:** Token was generated once at `clooks init` with no expiration or revocation mechanism.
|
|
16
|
+
|
|
17
|
+
**Resolution:** `clooks rotate-token` generates a new token, updates manifest and settings.json, and hot-reloads the daemon without restart.
|
|
18
|
+
|
|
19
|
+
## Manifest Reload Doesn't Trigger Session Reset -- RESOLVED
|
|
20
|
+
|
|
21
|
+
**Problem:** File watcher reloaded manifest but didn't reset session-isolated handler state.
|
|
22
|
+
|
|
23
|
+
**Resolution:** Manifest reload now diffs old vs new handlers. New handlers get fresh state, removed handlers get cleaned up, and changed handlers with `sessionIsolation: true` are reset.
|
|
24
|
+
|
|
25
|
+
## Health Endpoint Auth -- RESOLVED
|
|
26
|
+
|
|
27
|
+
**Problem:** `/health` bypassed auth, exposing operational details to unauthenticated clients.
|
|
28
|
+
|
|
29
|
+
**Resolution:** Split into `/health` (public, returns `{ status: "ok" }` only) and `/health/detail` (authenticated, returns uptime, handler count, plugin list).
|
|
30
|
+
|
|
31
|
+
## Rate Limiting on Auth Failures -- RESOLVED
|
|
32
|
+
|
|
33
|
+
**Problem:** Failed auth attempts were logged but not throttled.
|
|
34
|
+
|
|
35
|
+
**Resolution:** In-memory rate limiter rejects with 429 after repeated failed auth attempts within a time window. Resets on successful auth.
|
|
Binary file
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate clooks architecture diagram as PNG using matplotlib."""
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.patches as patches
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# --- Colors ---
|
|
9
|
+
BG = "#FFFFFF"
|
|
10
|
+
CLR_CLAUDE_FILL = "#E8F0FE"
|
|
11
|
+
CLR_CLAUDE_EDGE = "#4285F4"
|
|
12
|
+
CLR_DAEMON_FILL = "#F3E8FF"
|
|
13
|
+
CLR_DAEMON_EDGE = "#7C3AED"
|
|
14
|
+
CLR_SCRIPT_FILL = "#FFF7ED"
|
|
15
|
+
CLR_SCRIPT_EDGE = "#EA580C"
|
|
16
|
+
CLR_INLINE_FILL = "#ECFDF5"
|
|
17
|
+
CLR_INLINE_EDGE = "#059669"
|
|
18
|
+
CLR_LLM_FILL = "#FFF1F2"
|
|
19
|
+
CLR_LLM_EDGE = "#E11D48"
|
|
20
|
+
CLR_ARROW = "#475569"
|
|
21
|
+
CLR_TEXT = "#1E293B"
|
|
22
|
+
CLR_DIM = "#64748B"
|
|
23
|
+
CLR_ACCENT = "#7C3AED"
|
|
24
|
+
|
|
25
|
+
FONT = "sans-serif"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def rounded_box(ax, x, y, w, h, facecolor, edgecolor, lw=1.8):
|
|
29
|
+
"""Draw a FancyBboxPatch and return it."""
|
|
30
|
+
rect = patches.FancyBboxPatch(
|
|
31
|
+
(x, y), w, h,
|
|
32
|
+
boxstyle="round,pad=0.12",
|
|
33
|
+
facecolor=facecolor,
|
|
34
|
+
edgecolor=edgecolor,
|
|
35
|
+
linewidth=lw,
|
|
36
|
+
)
|
|
37
|
+
ax.add_patch(rect)
|
|
38
|
+
return rect
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def arrow(ax, x1, y1, x2, y2, label=None, label_offset_y=0.18, color=CLR_ARROW):
|
|
42
|
+
"""Draw an arrow with optional centered label."""
|
|
43
|
+
ax.annotate(
|
|
44
|
+
"", xy=(x2, y2), xytext=(x1, y1),
|
|
45
|
+
arrowprops=dict(
|
|
46
|
+
arrowstyle="->,head_width=0.25,head_length=0.12",
|
|
47
|
+
color=color, lw=1.6, connectionstyle="arc3,rad=0",
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
if label:
|
|
51
|
+
mx = (x1 + x2) / 2
|
|
52
|
+
my = (y1 + y2) / 2 + label_offset_y
|
|
53
|
+
ax.text(mx, my, label, ha="center", va="center",
|
|
54
|
+
fontsize=9, color=CLR_DIM, family=FONT,
|
|
55
|
+
bbox=dict(boxstyle="round,pad=0.2", facecolor="white",
|
|
56
|
+
edgecolor="#E2E8F0", linewidth=0.8, alpha=0.95))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
fig, ax = plt.subplots(figsize=(14, 6))
|
|
61
|
+
ax.set_xlim(0, 14)
|
|
62
|
+
ax.set_ylim(0, 6)
|
|
63
|
+
ax.set_aspect("equal")
|
|
64
|
+
ax.axis("off")
|
|
65
|
+
fig.patch.set_facecolor(BG)
|
|
66
|
+
|
|
67
|
+
# ── Title ──
|
|
68
|
+
ax.text(7, 5.65, "clooks architecture", ha="center", va="center",
|
|
69
|
+
fontsize=15, fontweight="bold", color=CLR_TEXT, family=FONT)
|
|
70
|
+
ax.text(7, 5.35, "Persistent hook runtime for Claude Code",
|
|
71
|
+
ha="center", va="center", fontsize=9.5, color=CLR_DIM, family=FONT)
|
|
72
|
+
|
|
73
|
+
# =====================================================================
|
|
74
|
+
# COLUMN 1 — Claude Code (left)
|
|
75
|
+
# =====================================================================
|
|
76
|
+
cx, cy, cw, ch = 0.4, 1.4, 2.8, 3.6
|
|
77
|
+
rounded_box(ax, cx, cy, cw, ch, CLR_CLAUDE_FILL, CLR_CLAUDE_EDGE)
|
|
78
|
+
|
|
79
|
+
ax.text(cx + cw / 2, cy + ch - 0.35, "Claude Code",
|
|
80
|
+
ha="center", va="center", fontsize=12, fontweight="bold",
|
|
81
|
+
color=CLR_CLAUDE_EDGE, family=FONT)
|
|
82
|
+
|
|
83
|
+
ax.text(cx + cw / 2, cy + ch - 0.75, "Hook Events:",
|
|
84
|
+
ha="center", va="center", fontsize=9, color=CLR_DIM, family=FONT)
|
|
85
|
+
|
|
86
|
+
events = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"]
|
|
87
|
+
for i, ev in enumerate(events):
|
|
88
|
+
ax.text(cx + cw / 2, cy + ch - 1.15 - i * 0.42, ev,
|
|
89
|
+
ha="center", va="center", fontsize=8.5, color=CLR_TEXT,
|
|
90
|
+
family="monospace")
|
|
91
|
+
|
|
92
|
+
# =====================================================================
|
|
93
|
+
# COLUMN 2 — clooks daemon (center)
|
|
94
|
+
# =====================================================================
|
|
95
|
+
dx, dy, dw, dh = 5.2, 1.0, 3.4, 4.0
|
|
96
|
+
rounded_box(ax, dx, dy, dw, dh, CLR_DAEMON_FILL, CLR_DAEMON_EDGE)
|
|
97
|
+
|
|
98
|
+
ax.text(dx + dw / 2, dy + dh - 0.35, "clooks daemon",
|
|
99
|
+
ha="center", va="center", fontsize=12, fontweight="bold",
|
|
100
|
+
color=CLR_DAEMON_EDGE, family=FONT)
|
|
101
|
+
|
|
102
|
+
ax.text(dx + dw / 2, dy + dh - 0.72, "localhost:7890",
|
|
103
|
+
ha="center", va="center", fontsize=9, color=CLR_DIM,
|
|
104
|
+
family="monospace")
|
|
105
|
+
|
|
106
|
+
features = ["Router", "Prefetch", "Dep Resolution", "Metrics", "Circuit Breaker"]
|
|
107
|
+
bullet_x = dx + 0.5
|
|
108
|
+
for i, feat in enumerate(features):
|
|
109
|
+
fy = dy + dh - 1.2 - i * 0.42
|
|
110
|
+
ax.text(bullet_x, fy, "\u2022 " + feat,
|
|
111
|
+
ha="left", va="center", fontsize=8.5, color=CLR_TEXT,
|
|
112
|
+
family=FONT)
|
|
113
|
+
|
|
114
|
+
# =====================================================================
|
|
115
|
+
# COLUMN 3 — Handler boxes (right)
|
|
116
|
+
# =====================================================================
|
|
117
|
+
hx, hw, hh = 10.2, 3.2, 0.9
|
|
118
|
+
handler_gap = 1.2
|
|
119
|
+
|
|
120
|
+
handlers = [
|
|
121
|
+
("Script Handler", "~5\u201335 ms", CLR_SCRIPT_FILL, CLR_SCRIPT_EDGE),
|
|
122
|
+
("Inline Handler", "<1 ms", CLR_INLINE_FILL, CLR_INLINE_EDGE),
|
|
123
|
+
("LLM Handler", "API call", CLR_LLM_FILL, CLR_LLM_EDGE),
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
# Position handlers: top one aligns near daemon top, stack downward
|
|
127
|
+
top_handler_y = 3.8
|
|
128
|
+
handler_centers = []
|
|
129
|
+
|
|
130
|
+
for i, (label, timing, fill, edge) in enumerate(handlers):
|
|
131
|
+
hy = top_handler_y - i * handler_gap
|
|
132
|
+
rounded_box(ax, hx, hy, hw, hh, fill, edge)
|
|
133
|
+
ax.text(hx + hw / 2, hy + hh / 2 + 0.12, label,
|
|
134
|
+
ha="center", va="center", fontsize=10.5, fontweight="bold",
|
|
135
|
+
color=edge, family=FONT)
|
|
136
|
+
ax.text(hx + hw / 2, hy + hh / 2 - 0.2, timing,
|
|
137
|
+
ha="center", va="center", fontsize=8.5, color=CLR_DIM,
|
|
138
|
+
family="monospace")
|
|
139
|
+
handler_centers.append(hy + hh / 2)
|
|
140
|
+
|
|
141
|
+
# =====================================================================
|
|
142
|
+
# ARROWS
|
|
143
|
+
# =====================================================================
|
|
144
|
+
|
|
145
|
+
# Claude Code -> Daemon (horizontal, at midpoint)
|
|
146
|
+
mid_y = cy + ch / 2
|
|
147
|
+
arrow(ax, cx + cw, mid_y, dx, mid_y, label="HTTP POST", label_offset_y=0.22)
|
|
148
|
+
|
|
149
|
+
# Daemon -> each Handler
|
|
150
|
+
daemon_right_x = dx + dw
|
|
151
|
+
for hc_y in handler_centers:
|
|
152
|
+
arrow(ax, daemon_right_x, hc_y, hx, hc_y)
|
|
153
|
+
|
|
154
|
+
# Daemon -> Claude Code return arrow (below the forward arrow)
|
|
155
|
+
return_y = mid_y - 0.55
|
|
156
|
+
arrow(ax, dx, return_y, cx + cw, return_y,
|
|
157
|
+
label="JSON response", label_offset_y=-0.25, color="#94A3B8")
|
|
158
|
+
|
|
159
|
+
# =====================================================================
|
|
160
|
+
# FOOTNOTE — bootstrap note
|
|
161
|
+
# =====================================================================
|
|
162
|
+
ax.text(7, 0.35, 'SessionStart fires "clooks ensure-running" to bootstrap daemon',
|
|
163
|
+
ha="center", va="center", fontsize=8.5, color=CLR_DIM, family=FONT,
|
|
164
|
+
style="italic",
|
|
165
|
+
bbox=dict(boxstyle="round,pad=0.3", facecolor="#F8FAFC",
|
|
166
|
+
edgecolor="#E2E8F0", linewidth=0.8))
|
|
167
|
+
|
|
168
|
+
# ── Save ──
|
|
169
|
+
out_path = Path(__file__).parent / "architecture.png"
|
|
170
|
+
fig.savefig(out_path, dpi=300, bbox_inches="tight",
|
|
171
|
+
facecolor=BG, edgecolor="none")
|
|
172
|
+
plt.close(fig)
|
|
173
|
+
print(f"Saved: {out_path} ({out_path.stat().st_size / 1024:.0f} KB)")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mauribadnights/clooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Persistent hook runtime for Claude Code — eliminates process spawning overhead and gives you observability",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clooks": "./dist/cli.js"
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
+
"docs",
|
|
35
36
|
"hooks",
|
|
37
|
+
"agents",
|
|
36
38
|
"README.md",
|
|
37
39
|
"LICENSE"
|
|
38
40
|
],
|