@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/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.2",
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
  ],