kickload-watcher-mcp 0.1.6 → 0.1.7
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/compliance-poller.js +216 -215
- package/email-sender.js +1 -5
- package/github-webhook.js +322 -321
- package/kickload-client.js +60 -41
- package/package.json +1 -1
- package/pipeline.js +83 -18
package/compliance-poller.js
CHANGED
|
@@ -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
|
-
|
|
8
|
-
let
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.warn("
|
|
26
|
-
console.warn("
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
pollingInterval
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
event.
|
|
124
|
-
event.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
event.messages ||
|
|
134
|
-
event.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/
|
|
187
|
-
/
|
|
188
|
-
/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
/@
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
/http
|
|
198
|
-
/
|
|
199
|
-
/
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
/@
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
package/email-sender.js
CHANGED
|
@@ -52,11 +52,7 @@ export async function verifyEmailService() {
|
|
|
52
52
|
console.log("✅ Email service verified");
|
|
53
53
|
} catch (err) {
|
|
54
54
|
console.log(""); // newline after the "..."
|
|
55
|
-
console.error("\n❌ Email verification failed");
|
|
56
|
-
console.error(" Fix:");
|
|
57
|
-
console.error(" • Use Gmail App Password (NOT your real password)");
|
|
58
|
-
console.error(" • Enable 2-Step Verification");
|
|
59
|
-
console.error(" • Check SMTP_USER and SMTP_PASS\n");
|
|
55
|
+
console.error("\n❌ Email verification failed — cannot start.");
|
|
60
56
|
console.error(` Provider : ${config.email.provider}`);
|
|
61
57
|
console.error(` User : ${config.email.smtp.user || "(none)"}`);
|
|
62
58
|
console.error(` Error : ${err.message}`);
|