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.
- package/README.md +164 -233
- package/compliance-poller.js +216 -215
- package/config.js +113 -103
- package/email-sender.js +137 -137
- package/github-webhook.js +322 -321
- package/index.js +266 -270
- package/kickload-client.js +260 -254
- package/package.json +51 -51
- package/pipeline.js +448 -448
- package/setup.js +193 -201
package/setup.js
CHANGED
|
@@ -1,201 +1,193 @@
|
|
|
1
|
-
import readline from "readline";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
|
|
5
|
-
const ENV_PATH = path.resolve(".env");
|
|
6
|
-
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* STARTUP FIX: After writing .env, we reload env and CONTINUE — never exit.
|
|
10
|
-
* The process stays alive and proceeds to start the watcher.
|
|
11
|
-
*/
|
|
12
|
-
export async function runFirstRunSetup() {
|
|
13
|
-
if (fs.existsSync(ENV_PATH)) {
|
|
14
|
-
console.log("⚠ .env already exists. Delete it to re-run setup.");
|
|
15
|
-
ensureGitignore();
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
printBanner();
|
|
20
|
-
console.log("No .env found — running one-time setup before starting.\n");
|
|
21
|
-
console.log("You will need:");
|
|
22
|
-
console.log(" • ANTHROPIC_API_KEY — console.anthropic.com");
|
|
23
|
-
console.log(" • KICKLOAD_API_TOKEN — kickload.neeyatai.com");
|
|
24
|
-
console.log(" • Gmail address + App Password (for result emails)");
|
|
25
|
-
console.log(" • NGROK_AUTHTOKEN — dashboard.ngrok.com (required for localhost backends)\n");
|
|
26
|
-
console.log("⚠️ Values are stored in a local .env file. Never commit it.\n");
|
|
27
|
-
|
|
28
|
-
const anthropicKey = await askRequired("ANTHROPIC_API_KEY : "
|
|
29
|
-
const kickloadToken = await askRequired("KICKLOAD_API_TOKEN : "
|
|
30
|
-
const smtpEmail = await askEmail( "Your Gmail address : ");
|
|
31
|
-
const smtpPass = await askRequired(
|
|
32
|
-
"Gmail App Password : (Use App Password, NOT your normal password)\n Create here: https://myaccount.google.com/apppasswords\n> "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.log("
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"",
|
|
53
|
-
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
║ KickLoad Watcher — First-Run Setup ║
|
|
195
|
-
║ Automated API Performance Testing (OneQA) ║
|
|
196
|
-
╚══════════════════════════════════════════════╝`);
|
|
197
|
-
}
|
|
198
|
-
runFirstRunSetup().catch(err => {
|
|
199
|
-
console.error("❌ Setup failed:", err.message);
|
|
200
|
-
process.exit(1);
|
|
201
|
-
});
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
const ENV_PATH = path.resolve(".env");
|
|
6
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* STARTUP FIX: After writing .env, we reload env and CONTINUE — never exit.
|
|
10
|
+
* The process stays alive and proceeds to start the watcher.
|
|
11
|
+
*/
|
|
12
|
+
export async function runFirstRunSetup() {
|
|
13
|
+
if (fs.existsSync(ENV_PATH)) {
|
|
14
|
+
console.log("⚠ .env already exists. Delete it to re-run setup.");
|
|
15
|
+
ensureGitignore();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
printBanner();
|
|
20
|
+
console.log("No .env found — running one-time setup before starting.\n");
|
|
21
|
+
console.log("You will need:");
|
|
22
|
+
console.log(" • ANTHROPIC_API_KEY — console.anthropic.com");
|
|
23
|
+
console.log(" • KICKLOAD_API_TOKEN — kickload.neeyatai.com");
|
|
24
|
+
console.log(" • Gmail address + App Password (for result emails)");
|
|
25
|
+
console.log(" • NGROK_AUTHTOKEN — dashboard.ngrok.com (required for localhost backends)\n");
|
|
26
|
+
console.log("⚠️ Values are stored in a local .env file. Never commit it.\n");
|
|
27
|
+
|
|
28
|
+
const anthropicKey = await askRequired("ANTHROPIC_API_KEY : ");
|
|
29
|
+
const kickloadToken = await askRequired("KICKLOAD_API_TOKEN : ");
|
|
30
|
+
const smtpEmail = await askEmail( "Your Gmail address : ");
|
|
31
|
+
const smtpPass = await askRequired(
|
|
32
|
+
"Gmail App Password : (Use App Password, NOT your normal password)\n Create here: https://myaccount.google.com/apppasswords\n> ");
|
|
33
|
+
const devEmail = await askEmail( "Developer email : ");
|
|
34
|
+
const ngrokToken = await askRequired(
|
|
35
|
+
"NGROK_AUTHTOKEN : (Required for localhost testing)\n Get it from: https://dashboard.ngrok.com/get-started/your-authtoken\n> ");
|
|
36
|
+
|
|
37
|
+
if (!ngrokToken) {
|
|
38
|
+
console.log("\n⚠️ ngrok skipped.");
|
|
39
|
+
console.log(" Localhost backends will attempt unauthenticated tunnels (may be rate-limited).");
|
|
40
|
+
console.log(" For reliable testing, add to .env later:");
|
|
41
|
+
console.log(" NGROK_AUTHTOKEN=<your_token> (dashboard.ngrok.com)");
|
|
42
|
+
console.log("");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lines = [
|
|
46
|
+
"# KickLoad Watcher — generated by setup",
|
|
47
|
+
"# ⚠️ Keep this file SECRET — never commit to git",
|
|
48
|
+
"",
|
|
49
|
+
`ANTHROPIC_API_KEY=${anthropicKey}`,
|
|
50
|
+
`KICKLOAD_API_TOKEN=${kickloadToken}`,
|
|
51
|
+
`KICKLOAD_BASE_URL=https://kickload.neeyatai.com/api`,
|
|
52
|
+
"",
|
|
53
|
+
`# ngrok: auto-enabled for localhost backends regardless of this flag`,
|
|
54
|
+
`NGROK_AUTHTOKEN=${ngrokToken || ""}`,
|
|
55
|
+
"",
|
|
56
|
+
`EMAIL_PROVIDER=smtp`,
|
|
57
|
+
`EMAIL_FROM_NAME=KickLoad Watcher`,
|
|
58
|
+
`EMAIL_FROM_ADDRESS=${smtpEmail}`,
|
|
59
|
+
`SMTP_HOST=smtp.gmail.com`,
|
|
60
|
+
`SMTP_PORT=465`,
|
|
61
|
+
`SMTP_USER=${smtpEmail}`,
|
|
62
|
+
`SMTP_PASS=${smtpPass}`,
|
|
63
|
+
"",
|
|
64
|
+
`DEFAULT_DEVELOPER_EMAIL=${devEmail}`,
|
|
65
|
+
"",
|
|
66
|
+
`WATCH_PATHS=.`,
|
|
67
|
+
`TRIGGER_MODE=claudecode`,
|
|
68
|
+
`LOG_LEVEL=info`,
|
|
69
|
+
"",
|
|
70
|
+
"# Optional: override auto-detected backend URL",
|
|
71
|
+
"# TARGET_API_BASE_URL=https://your-api.example.com",
|
|
72
|
+
"",
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(ENV_PATH, lines.join("\n"), "utf-8");
|
|
76
|
+
ensureGitignore();
|
|
77
|
+
|
|
78
|
+
// Re-load env immediately so the rest of the process sees the values.
|
|
79
|
+
const { default: dotenv } = await import("dotenv");
|
|
80
|
+
dotenv.config({ override: true });
|
|
81
|
+
|
|
82
|
+
// STARTUP FIX: Do NOT exit — continue execution
|
|
83
|
+
console.log("\n✅ Setup complete — starting KickLoad Watcher now...\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function ensureGitignore() {
|
|
87
|
+
const gitignorePath = path.resolve(".gitignore");
|
|
88
|
+
const required = [".env", ".bin/"];
|
|
89
|
+
|
|
90
|
+
let content = "";
|
|
91
|
+
try {
|
|
92
|
+
if (fs.existsSync(gitignorePath)) content = fs.readFileSync(gitignorePath, "utf-8");
|
|
93
|
+
} catch { /* non-fatal */ }
|
|
94
|
+
|
|
95
|
+
const existing = content.split("\n").map(l => l.trim());
|
|
96
|
+
const missing = required.filter(r => !existing.includes(r));
|
|
97
|
+
if (missing.length === 0) return;
|
|
98
|
+
|
|
99
|
+
const prefix = content && !content.endsWith("\n") ? "\n" : "";
|
|
100
|
+
fs.appendFileSync(gitignorePath, `${prefix}# KickLoad Watcher\n${missing.join("\n")}\n`, "utf-8");
|
|
101
|
+
console.warn("⚠️ Added .env and .bin/ to .gitignore — never commit your API keys.");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Input helpers ─────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
async function askRequired(prompt) {
|
|
107
|
+
while (true) {
|
|
108
|
+
const val = await inputPlain(prompt);
|
|
109
|
+
if (val.length > 0) return val;
|
|
110
|
+
console.log(" This field is required — please try again.");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function askEmail(prompt) {
|
|
115
|
+
while (true) {
|
|
116
|
+
const val = await inputPlain(prompt);
|
|
117
|
+
if (EMAIL_RE.test(val)) return val;
|
|
118
|
+
console.log(" Invalid email address — please try again.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function askOptional(prompt) {
|
|
123
|
+
return inputPlain(prompt);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function inputPlain(prompt) {
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
129
|
+
rl.question(prompt, (a) => { rl.close(); resolve(a.trim()); });
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function inputMasked(prompt) {
|
|
134
|
+
if (!process.stdin.isTTY) return inputPlain(prompt);
|
|
135
|
+
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
process.stdout.write(prompt);
|
|
138
|
+
|
|
139
|
+
let value = "";
|
|
140
|
+
let done = false;
|
|
141
|
+
|
|
142
|
+
const cleanup = () => {
|
|
143
|
+
if (done) return;
|
|
144
|
+
done = true;
|
|
145
|
+
try { process.stdin.setRawMode(false); } catch { /* ignore */ }
|
|
146
|
+
process.stdin.removeListener("data", handler);
|
|
147
|
+
process.stdin.pause();
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handler = (raw) => {
|
|
151
|
+
const char = raw.toString();
|
|
152
|
+
|
|
153
|
+
if (char === "\u0003") {
|
|
154
|
+
cleanup();
|
|
155
|
+
process.stdout.write("\n");
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (char === "\r" || char === "\n") {
|
|
160
|
+
cleanup();
|
|
161
|
+
process.stdout.write("\n");
|
|
162
|
+
resolve(value.trim());
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (char === "\u007F" || char === "\b") {
|
|
167
|
+
if (value.length > 0) {
|
|
168
|
+
value = value.slice(0, -1);
|
|
169
|
+
process.stdout.clearLine(0);
|
|
170
|
+
process.stdout.cursorTo(0);
|
|
171
|
+
process.stdout.write(prompt + "*".repeat(value.length));
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
value += char;
|
|
177
|
+
process.stdout.write("*");
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
process.stdin.setRawMode(true);
|
|
181
|
+
process.stdin.resume();
|
|
182
|
+
process.stdin.setEncoding("utf8");
|
|
183
|
+
process.stdin.on("data", handler);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function printBanner() {
|
|
188
|
+
console.log(`
|
|
189
|
+
╔══════════════════════════════════════════════╗
|
|
190
|
+
║ KickLoad Watcher — First-Run Setup ║
|
|
191
|
+
║ Automated API Performance Testing (OneQA) ║
|
|
192
|
+
╚══════════════════════════════════════════════╝`);
|
|
193
|
+
}
|