kickload-watcher-mcp 0.1.0 → 0.1.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.
@@ -1,215 +1,216 @@
1
- // compliance-poller.js — Polls Anthropic Compliance API for new Claude Code sessions
2
- // Enterprise plan required. Enable at:
3
- // platform.claude.com → Organization Settings → Data and Privacy → Compliance API
4
-
5
- import { config } from "./config.js";
6
-
7
- let lastEventTimestamp = null;
8
- let pollingInterval = null;
9
- const sessionCallbacks = [];
10
-
11
- /**
12
- * Register a callback to be called when a new API-related session is detected.
13
- * Callback receives: { userId, sessionId, generatedCode, prompt, timestamp }
14
- */
15
- export function onNewSession(callback) {
16
- sessionCallbacks.push(callback);
17
- }
18
-
19
- /**
20
- * Start polling the Compliance API on the configured interval.
21
- */
22
- export function startCompliancePoller() {
23
- if (!config.anthropic.complianceApiKey) {
24
- console.warn("⚠️ ANTHROPIC_COMPLIANCE_API_KEY not set.");
25
- console.warn(" Compliance API polling disabled.");
26
- console.warn(" File watcher will be the only trigger.");
27
- return;
28
- }
29
-
30
- console.log(
31
- `🔍 Compliance API poller started (every ${config.anthropic.pollIntervalMs / 1000}s)`
32
- );
33
-
34
- // Set initial timestamp to now so we only pick up new events
35
- lastEventTimestamp = new Date().toISOString();
36
-
37
- pollingInterval = setInterval(async () => {
38
- try {
39
- await pollComplianceEvents();
40
- } catch (err) {
41
- console.error("❌ Compliance API poll failed:", err.message);
42
- }
43
- }, config.anthropic.pollIntervalMs);
44
-
45
- // Run immediately on start
46
- pollComplianceEvents().catch((err) =>
47
- console.error("❌ Initial compliance poll failed:", err.message)
48
- );
49
- }
50
-
51
- /**
52
- * Stop the compliance poller.
53
- */
54
- export function stopCompliancePoller() {
55
- if (pollingInterval) {
56
- clearInterval(pollingInterval);
57
- pollingInterval = null;
58
- console.log("⏹ Compliance API poller stopped.");
59
- }
60
- }
61
-
62
- /**
63
- * Fetch recent events from the Compliance API and filter for API-related code.
64
- */
65
- async function pollComplianceEvents() {
66
- const url = new URL(`${config.anthropic.complianceBaseUrl}/organizations/audit-log`);
67
-
68
- if (lastEventTimestamp) {
69
- url.searchParams.set("after", lastEventTimestamp);
70
- }
71
-
72
- url.searchParams.set("limit", "50");
73
-
74
- const response = await fetch(url.toString(), {
75
- method: "GET",
76
- headers: {
77
- "anthropic-organization-key": config.anthropic.complianceApiKey,
78
- "Content-Type": "application/json",
79
- },
80
- });
81
-
82
- if (!response.ok) {
83
- const body = await response.text();
84
- throw new Error(`Compliance API ${response.status}: ${body}`);
85
- }
86
-
87
- const data = await response.json();
88
- const events = data.events || data.data || [];
89
-
90
- if (events.length === 0) return;
91
-
92
- // Update timestamp to latest event so we don't re-process
93
- const latest = events[events.length - 1];
94
- if (latest.created_at) {
95
- lastEventTimestamp = latest.created_at;
96
- }
97
-
98
- // Filter events that contain API-related code
99
- for (const event of events) {
100
- const session = extractApiSession(event);
101
- if (session) {
102
- console.log(`📡 New API session detected from user: ${session.userId}`);
103
- for (const cb of sessionCallbacks) {
104
- try {
105
- await cb(session);
106
- } catch (err) {
107
- console.error("❌ Session callback error:", err.message);
108
- }
109
- }
110
- }
111
- }
112
- }
113
-
114
- /**
115
- * Extract relevant session data from a compliance event.
116
- * Returns null if the event doesn't contain API-related code.
117
- */
118
- function extractApiSession(event) {
119
- try {
120
- // Compliance API event structure
121
- const userId =
122
- event.actor?.id ||
123
- event.user_id ||
124
- event.actor?.user_id ||
125
- null;
126
-
127
- const sessionId = event.id || event.session_id || null;
128
- const timestamp = event.created_at || new Date().toISOString();
129
-
130
- // Get message content — structure varies by event type
131
- const messages =
132
- event.payload?.messages ||
133
- event.messages ||
134
- event.content?.messages ||
135
- [];
136
-
137
- // Look through messages for generated code with API endpoints
138
- let generatedCode = null;
139
- let userPrompt = null;
140
-
141
- for (const msg of messages) {
142
- // Capture the user's original prompt
143
- if (msg.role === "user" && typeof msg.content === "string") {
144
- userPrompt = msg.content;
145
- }
146
-
147
- // Look for assistant response containing API code
148
- if (msg.role === "assistant") {
149
- const content =
150
- typeof msg.content === "string"
151
- ? msg.content
152
- : Array.isArray(msg.content)
153
- ? msg.content.map((b) => b.text || "").join("\n")
154
- : "";
155
-
156
- // Detect if this looks like API endpoint code
157
- if (containsApiCode(content)) {
158
- generatedCode = content;
159
- }
160
- }
161
- }
162
-
163
- // Only return if we found actual API code
164
- if (!generatedCode || !userId) return null;
165
-
166
- return {
167
- userId,
168
- sessionId,
169
- generatedCode,
170
- prompt: userPrompt || "No prompt captured",
171
- timestamp,
172
- source: "compliance_api",
173
- };
174
- } catch (err) {
175
- return null;
176
- }
177
- }
178
-
179
- /**
180
- * Detect if code content contains API endpoint definitions.
181
- * Covers Express, Flask, FastAPI, Django, Spring, Go, Rails patterns.
182
- */
183
- function containsApiCode(content) {
184
- const apiPatterns = [
185
- // JavaScript/Node.js (Express, Fastify, Hapi)
186
- /app\.(get|post|put|patch|delete|use)\s*\(\s*['"`]/i,
187
- /router\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
188
- /fastify\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
189
-
190
- // Python (Flask, FastAPI, Django)
191
- /@app\.route\s*\(\s*['"`]/i,
192
- /@router\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
193
- /path\s*\(\s*['"`][^'"]+['"`]\s*,/i,
194
- /@app\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
195
-
196
- // Go (net/http, gin, echo)
197
- /http\.HandleFunc\s*\(\s*['"`]/i,
198
- /r\.(GET|POST|PUT|PATCH|DELETE)\s*\(\s*['"`]/i,
199
- /e\.(GET|POST|PUT|PATCH|DELETE)\s*\(\s*['"`]/i,
200
-
201
- // Java/Spring
202
- /@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*['"`]/i,
203
- /@RequestMapping\s*\(/i,
204
-
205
- // Ruby on Rails
206
- /resources\s+:/i,
207
- /get\s+['"`][^'"]+['"`]\s*,\s*to:/i,
208
-
209
- // General endpoint patterns
210
- /\/api\//i,
211
- /endpoint/i,
212
- ];
213
-
214
- return apiPatterns.some((pattern) => pattern.test(content));
215
- }
1
+ // compliance-poller.js — Polls Anthropic Compliance API for new Claude Code sessions
2
+ // Enterprise plan required. Enable at:
3
+ // platform.claude.com → Organization Settings → Data and Privacy → Compliance API
4
+
5
+ import { config } from "./config.js";
6
+ import fetch from "node-fetch";
7
+
8
+ let lastEventTimestamp = null;
9
+ let pollingInterval = null;
10
+ const sessionCallbacks = [];
11
+
12
+ /**
13
+ * Register a callback to be called when a new API-related session is detected.
14
+ * Callback receives: { userId, sessionId, generatedCode, prompt, timestamp }
15
+ */
16
+ export function onNewSession(callback) {
17
+ sessionCallbacks.push(callback);
18
+ }
19
+
20
+ /**
21
+ * Start polling the Compliance API on the configured interval.
22
+ */
23
+ export function startCompliancePoller() {
24
+ if (!config.anthropic.complianceApiKey) {
25
+ console.warn("⚠️ ANTHROPIC_COMPLIANCE_API_KEY not set.");
26
+ console.warn(" Compliance API polling disabled.");
27
+ console.warn(" File watcher will be the only trigger.");
28
+ return;
29
+ }
30
+
31
+ console.log(
32
+ `🔍 Compliance API poller started (every ${config.anthropic.pollIntervalMs / 1000}s)`
33
+ );
34
+
35
+ // Set initial timestamp to now so we only pick up new events
36
+ lastEventTimestamp = new Date().toISOString();
37
+
38
+ pollingInterval = setInterval(async () => {
39
+ try {
40
+ await pollComplianceEvents();
41
+ } catch (err) {
42
+ console.error("❌ Compliance API poll failed:", err.message);
43
+ }
44
+ }, config.anthropic.pollIntervalMs);
45
+
46
+ // Run immediately on start
47
+ pollComplianceEvents().catch((err) =>
48
+ console.error("❌ Initial compliance poll failed:", err.message)
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Stop the compliance poller.
54
+ */
55
+ export function stopCompliancePoller() {
56
+ if (pollingInterval) {
57
+ clearInterval(pollingInterval);
58
+ pollingInterval = null;
59
+ console.log("⏹ Compliance API poller stopped.");
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Fetch recent events from the Compliance API and filter for API-related code.
65
+ */
66
+ async function pollComplianceEvents() {
67
+ const url = new URL(`${config.anthropic.complianceBaseUrl}/organizations/audit-log`);
68
+
69
+ if (lastEventTimestamp) {
70
+ url.searchParams.set("after", lastEventTimestamp);
71
+ }
72
+
73
+ url.searchParams.set("limit", "50");
74
+
75
+ const response = await fetch(url.toString(), {
76
+ method: "GET",
77
+ headers: {
78
+ "anthropic-organization-key": config.anthropic.complianceApiKey,
79
+ "Content-Type": "application/json",
80
+ },
81
+ });
82
+
83
+ if (!response.ok) {
84
+ const body = await response.text();
85
+ throw new Error(`Compliance API ${response.status}: ${body}`);
86
+ }
87
+
88
+ const data = await response.json();
89
+ const events = data.events || data.data || [];
90
+
91
+ if (events.length === 0) return;
92
+
93
+ // Update timestamp to latest event so we don't re-process
94
+ const latest = events[events.length - 1];
95
+ if (latest.created_at) {
96
+ lastEventTimestamp = latest.created_at;
97
+ }
98
+
99
+ // Filter events that contain API-related code
100
+ for (const event of events) {
101
+ const session = extractApiSession(event);
102
+ if (session) {
103
+ console.log(`📡 New API session detected from user: ${session.userId}`);
104
+ for (const cb of sessionCallbacks) {
105
+ try {
106
+ await cb(session);
107
+ } catch (err) {
108
+ console.error("❌ Session callback error:", err.message);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Extract relevant session data from a compliance event.
117
+ * Returns null if the event doesn't contain API-related code.
118
+ */
119
+ function extractApiSession(event) {
120
+ try {
121
+ // Compliance API event structure
122
+ const userId =
123
+ event.actor?.id ||
124
+ event.user_id ||
125
+ event.actor?.user_id ||
126
+ null;
127
+
128
+ const sessionId = event.id || event.session_id || null;
129
+ const timestamp = event.created_at || new Date().toISOString();
130
+
131
+ // Get message content — structure varies by event type
132
+ const messages =
133
+ event.payload?.messages ||
134
+ event.messages ||
135
+ event.content?.messages ||
136
+ [];
137
+
138
+ // Look through messages for generated code with API endpoints
139
+ let generatedCode = null;
140
+ let userPrompt = null;
141
+
142
+ for (const msg of messages) {
143
+ // Capture the user's original prompt
144
+ if (msg.role === "user" && typeof msg.content === "string") {
145
+ userPrompt = msg.content;
146
+ }
147
+
148
+ // Look for assistant response containing API code
149
+ if (msg.role === "assistant") {
150
+ const content =
151
+ typeof msg.content === "string"
152
+ ? msg.content
153
+ : Array.isArray(msg.content)
154
+ ? msg.content.map((b) => b.text || "").join("\n")
155
+ : "";
156
+
157
+ // Detect if this looks like API endpoint code
158
+ if (containsApiCode(content)) {
159
+ generatedCode = content;
160
+ }
161
+ }
162
+ }
163
+
164
+ // Only return if we found actual API code
165
+ if (!generatedCode || !userId) return null;
166
+
167
+ return {
168
+ userId,
169
+ sessionId,
170
+ generatedCode,
171
+ prompt: userPrompt || "No prompt captured",
172
+ timestamp,
173
+ source: "compliance_api",
174
+ };
175
+ } catch (err) {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Detect if code content contains API endpoint definitions.
182
+ * Covers Express, Flask, FastAPI, Django, Spring, Go, Rails patterns.
183
+ */
184
+ function containsApiCode(content) {
185
+ const apiPatterns = [
186
+ // JavaScript/Node.js (Express, Fastify, Hapi)
187
+ /app\.(get|post|put|patch|delete|use)\s*\(\s*['"`]/i,
188
+ /router\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
189
+ /fastify\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
190
+
191
+ // Python (Flask, FastAPI, Django)
192
+ /@app\.route\s*\(\s*['"`]/i,
193
+ /@router\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
194
+ /path\s*\(\s*['"`][^'"]+['"`]\s*,/i,
195
+ /@app\.(get|post|put|patch|delete)\s*\(\s*['"`]/i,
196
+
197
+ // Go (net/http, gin, echo)
198
+ /http\.HandleFunc\s*\(\s*['"`]/i,
199
+ /r\.(GET|POST|PUT|PATCH|DELETE)\s*\(\s*['"`]/i,
200
+ /e\.(GET|POST|PUT|PATCH|DELETE)\s*\(\s*['"`]/i,
201
+
202
+ // Java/Spring
203
+ /@(Get|Post|Put|Delete|Patch)Mapping\s*\(\s*['"`]/i,
204
+ /@RequestMapping\s*\(/i,
205
+
206
+ // Ruby on Rails
207
+ /resources\s+:/i,
208
+ /get\s+['"`][^'"]+['"`]\s*,\s*to:/i,
209
+
210
+ // General endpoint patterns
211
+ /\/api\//i,
212
+ /endpoint/i,
213
+ ];
214
+
215
+ return apiPatterns.some((pattern) => pattern.test(content));
216
+ }