@mauribadnights/clooks 0.1.0 → 0.2.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.
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ // clooks file watcher — watch manifest.yaml for changes and hot-reload
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.startWatcher = startWatcher;
5
+ exports.stopWatcher = stopWatcher;
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const DEBOUNCE_MS = 500;
9
+ /**
10
+ * Watch manifest.yaml for changes.
11
+ * Calls onReload when changes detected (debounced).
12
+ * If the manifest file doesn't exist, watches the config directory for its creation.
13
+ */
14
+ function startWatcher(manifestPath, onReload, onError) {
15
+ let lastChange = 0;
16
+ // If manifest exists, watch it directly
17
+ if ((0, fs_1.existsSync)(manifestPath)) {
18
+ return watchFile(manifestPath, onReload, onError);
19
+ }
20
+ // Manifest doesn't exist — watch the config directory for its creation
21
+ const configDir = (0, path_1.dirname)(manifestPath);
22
+ if (!(0, fs_1.existsSync)(configDir)) {
23
+ try {
24
+ (0, fs_1.mkdirSync)(configDir, { recursive: true });
25
+ }
26
+ catch {
27
+ // Can't create config dir — give up
28
+ if (onError)
29
+ onError(new Error(`Cannot create config directory: ${configDir}`));
30
+ return null;
31
+ }
32
+ }
33
+ const baseName = manifestPath.split('/').pop() ?? manifestPath.split('\\').pop() ?? '';
34
+ let dirWatcher = null;
35
+ try {
36
+ dirWatcher = (0, fs_1.watch)(configDir, (eventType, filename) => {
37
+ if (filename !== baseName)
38
+ return;
39
+ if (!(0, fs_1.existsSync)(manifestPath))
40
+ return;
41
+ const now = Date.now();
42
+ if (now - lastChange < DEBOUNCE_MS)
43
+ return;
44
+ lastChange = now;
45
+ // Manifest appeared — close directory watcher, start file watcher
46
+ try {
47
+ dirWatcher?.close();
48
+ }
49
+ catch {
50
+ // ignore
51
+ }
52
+ // Switch to watching the file directly
53
+ const fileWatcher = watchFile(manifestPath, onReload, onError);
54
+ if (fileWatcher) {
55
+ // Copy the ref so stopWatcher can close it (caller still holds the dir watcher ref)
56
+ // We can't replace the caller's reference, but the dir watcher is closed.
57
+ // The onReload fires so the caller picks up the new manifest.
58
+ }
59
+ try {
60
+ onReload();
61
+ }
62
+ catch (err) {
63
+ if (onError)
64
+ onError(err instanceof Error ? err : new Error(String(err)));
65
+ }
66
+ });
67
+ dirWatcher.on('error', (err) => {
68
+ if (onError)
69
+ onError(err);
70
+ });
71
+ return dirWatcher;
72
+ }
73
+ catch {
74
+ if (onError)
75
+ onError(new Error(`Failed to watch config directory: ${configDir}`));
76
+ return null;
77
+ }
78
+ }
79
+ /** Watch an existing file directly. */
80
+ function watchFile(filePath, onReload, onError) {
81
+ let lastChange = 0;
82
+ try {
83
+ const watcher = (0, fs_1.watch)(filePath, (eventType) => {
84
+ if (eventType !== 'change' && eventType !== 'rename')
85
+ return;
86
+ const now = Date.now();
87
+ if (now - lastChange < DEBOUNCE_MS)
88
+ return;
89
+ lastChange = now;
90
+ try {
91
+ onReload();
92
+ }
93
+ catch (err) {
94
+ if (onError)
95
+ onError(err instanceof Error ? err : new Error(String(err)));
96
+ }
97
+ });
98
+ watcher.on('error', (err) => {
99
+ if (onError)
100
+ onError(err);
101
+ });
102
+ return watcher;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * Stop watching for file changes.
110
+ */
111
+ function stopWatcher(watcher) {
112
+ if (watcher) {
113
+ try {
114
+ watcher.close();
115
+ }
116
+ catch {
117
+ // Ignore close errors
118
+ }
119
+ }
120
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauribadnights/clooks",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
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"
@@ -11,7 +11,8 @@
11
11
  "build": "tsc",
12
12
  "dev": "tsc --watch",
13
13
  "test": "vitest run",
14
- "test:watch": "vitest"
14
+ "test:watch": "vitest",
15
+ "bench": "npx tsx benchmarks/bench.ts"
15
16
  },
16
17
  "license": "MIT",
17
18
  "author": "mauribadnights",
@@ -38,8 +39,17 @@
38
39
  "commander": "^14.0.3",
39
40
  "yaml": "^2.8.3"
40
41
  },
42
+ "peerDependencies": {
43
+ "@anthropic-ai/sdk": ">=0.30.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@anthropic-ai/sdk": {
47
+ "optional": true
48
+ }
49
+ },
41
50
  "devDependencies": {
42
51
  "@types/node": "^25.5.0",
52
+ "tsx": "^4.21.0",
43
53
  "typescript": "^6.0.2",
44
54
  "vitest": "^4.1.1"
45
55
  }