kojee-mcp 0.5.3 → 0.5.6
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 +112 -5
- package/dist/{chunk-YEC7IHIG.js → chunk-2BDAM3TH.js} +92 -523
- package/dist/chunk-2MIISF2W.js +35 -0
- package/dist/chunk-3XDJOHMZ.js +223 -0
- package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
- package/dist/chunk-6SK6ITFE.js +142 -0
- package/dist/chunk-GI2CKKBL.js +46 -0
- package/dist/chunk-HIZ4NDWN.js +141 -0
- package/dist/chunk-LDZXU3DW.js +170 -0
- package/dist/{resubscribe-SLZNA76S.js → chunk-OT2GILXC.js} +1 -0
- package/dist/{chunk-WBMX4CHB.js → chunk-UEGQGXPY.js} +57 -40
- package/dist/chunk-V5VZPYMZ.js +185 -0
- package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
- package/dist/cli.js +47 -24
- package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
- package/dist/control-token-4BUCTYQB.js +13 -0
- package/dist/{doctor-TSHOMT5X.js → doctor-QCQDFLEH.js} +30 -17
- package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-NZ53ROQA.js} +12 -5
- package/dist/ensure-join-7AEDJMPE.js +96 -0
- package/dist/gateway-client-93P1E0CZ.d.ts +92 -0
- package/dist/{hook-server-QF5JVUHV.js → hook-server-37E2LUKJ.js} +91 -0
- package/dist/index.d.ts +18 -15
- package/dist/index.js +9 -3
- package/dist/lib.d.ts +427 -0
- package/dist/lib.js +44 -0
- package/dist/reconnect-scheduler-JSXCJKQP.js +26 -0
- package/dist/resubscribe-G5OGDZJD.js +6 -0
- package/dist/send-cli-C2F4WTBN.js +72 -0
- package/dist/{stop-hook-SEPWWETV.js → stop-hook-TRAMQYNE.js} +16 -8
- package/dist/{tail-stream-BYKO4DW6.js → tail-stream-VUZBYKXS.js} +4 -3
- package/dist/{user-prompt-submit-hook-ARPEO6FF.js → user-prompt-submit-hook-ZD2XKN7U.js} +7 -1
- package/dist/webhook-config-O4WMQ532.js +20 -0
- package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-NWGCUDGY.js} +28 -5
- package/dist/{wizard-7KHD5JT4.js → wizard-OSOAY4GO.js} +64 -27
- package/package.json +11 -2
- package/dist/chunk-F7L25L2J.js +0 -60
- package/dist/webhook-config-5TLLX7RA.js +0 -10
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WEBHOOK_DEFAULT_SIGNATURE_HEADER,
|
|
3
|
+
WEBHOOK_DEFAULT_SIGNATURE_PREFIX
|
|
4
|
+
} from "./chunk-V5VZPYMZ.js";
|
|
5
|
+
|
|
1
6
|
// src/tandem/webhook-sink.ts
|
|
2
7
|
import crypto from "crypto";
|
|
3
8
|
import { ulid } from "ulidx";
|
|
@@ -26,12 +31,15 @@ function createWebhookSink(config, options = {}) {
|
|
|
26
31
|
const maxQueue = options.maxQueue ?? DEFAULT_MAX_QUEUE;
|
|
27
32
|
const backoffMs = options.backoffMs ?? defaultBackoffMs;
|
|
28
33
|
const log = options.log ?? ((line) => console.error(`[webhook] ${line}`));
|
|
34
|
+
const signatureHeader = config.signatureHeader ?? WEBHOOK_DEFAULT_SIGNATURE_HEADER;
|
|
35
|
+
const signaturePrefix = config.signaturePrefix ?? WEBHOOK_DEFAULT_SIGNATURE_PREFIX;
|
|
29
36
|
const queue = [];
|
|
30
37
|
let inFlight = false;
|
|
31
38
|
let stopped = false;
|
|
32
39
|
let draining = false;
|
|
33
40
|
const stats = {
|
|
34
41
|
delivered: 0,
|
|
42
|
+
deliveredUnconfirmed: 0,
|
|
35
43
|
dropped: 0,
|
|
36
44
|
overflowDropped: 0,
|
|
37
45
|
queueDepth: 0,
|
|
@@ -51,20 +59,26 @@ function createWebhookSink(config, options = {}) {
|
|
|
51
59
|
}
|
|
52
60
|
function prepare(event) {
|
|
53
61
|
const body = JSON.stringify(event);
|
|
54
|
-
const signature = computeWebhookSignature(config.secret, body);
|
|
62
|
+
const signature = signaturePrefix + computeWebhookSignature(config.secret, body);
|
|
55
63
|
const deliveryId = event.id || ulid();
|
|
56
64
|
return { event, body, signature, deliveryId };
|
|
57
65
|
}
|
|
58
66
|
async function attempt(item) {
|
|
59
67
|
const controller = new AbortController();
|
|
60
|
-
|
|
68
|
+
let timedOut = false;
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
timedOut = true;
|
|
71
|
+
controller.abort();
|
|
72
|
+
}, config.timeoutMs);
|
|
61
73
|
try {
|
|
62
74
|
const res = await fetchImpl(config.url, {
|
|
63
75
|
method: "POST",
|
|
64
76
|
headers: {
|
|
65
77
|
"Content-Type": "application/json",
|
|
66
|
-
// hex SHA-256 HMAC of the RAW body bytes — the receiver
|
|
67
|
-
|
|
78
|
+
// (prefix +) hex SHA-256 HMAC of the RAW body bytes — the receiver
|
|
79
|
+
// re-verifies. Header name defaults to X-Kojee-Signature; the
|
|
80
|
+
// GitHub-convention preset emits X-Hub-Signature-256 / sha256=.
|
|
81
|
+
[signatureHeader]: item.signature,
|
|
68
82
|
"X-Kojee-Delivery": item.deliveryId
|
|
69
83
|
},
|
|
70
84
|
body: item.body,
|
|
@@ -72,7 +86,7 @@ function createWebhookSink(config, options = {}) {
|
|
|
72
86
|
});
|
|
73
87
|
return classifyStatus(res.status);
|
|
74
88
|
} catch {
|
|
75
|
-
return "retry";
|
|
89
|
+
return timedOut ? "timeout" : "retry";
|
|
76
90
|
} finally {
|
|
77
91
|
clearTimeout(timer);
|
|
78
92
|
}
|
|
@@ -87,6 +101,15 @@ function createWebhookSink(config, options = {}) {
|
|
|
87
101
|
stats.lastAttemptAt = Date.now();
|
|
88
102
|
return;
|
|
89
103
|
}
|
|
104
|
+
if (outcome === "timeout") {
|
|
105
|
+
stats.deliveredUnconfirmed += 1;
|
|
106
|
+
stats.lastOutcome = "delivered-unconfirmed";
|
|
107
|
+
stats.lastAttemptAt = Date.now();
|
|
108
|
+
log(
|
|
109
|
+
`delivery=${item.deliveryId} delivered-unconfirmed (receiver slow: no response within ${config.timeoutMs}ms) \u2014 not retrying, a re-POST could duplicate the receiver's side effects`
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
90
113
|
if (outcome === "permanent") {
|
|
91
114
|
stats.dropped += 1;
|
|
92
115
|
stats.lastOutcome = "dropped";
|
|
@@ -1,31 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearRuntimeRecord,
|
|
3
|
+
readRecordedRuntime,
|
|
4
|
+
recordRuntime
|
|
5
|
+
} from "./chunk-EW72ZNQL.js";
|
|
1
6
|
import {
|
|
2
7
|
buildCodexMcpServerTable,
|
|
3
8
|
buildCodexStopHookBlock,
|
|
4
9
|
removeCodexConfig,
|
|
5
10
|
writeCodexConfig
|
|
6
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-64EOLZNI.js";
|
|
12
|
+
import {
|
|
13
|
+
kojeeHomeDir
|
|
14
|
+
} from "./chunk-SQL56SEB.js";
|
|
7
15
|
import {
|
|
8
16
|
WIZARD_RUNTIMES,
|
|
9
17
|
isWizardRuntime
|
|
10
18
|
} from "./chunk-LVL25VLO.js";
|
|
11
19
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "./chunk-EW72ZNQL.js";
|
|
16
|
-
import {
|
|
17
|
-
kojeeHomeDir
|
|
18
|
-
} from "./chunk-SQL56SEB.js";
|
|
20
|
+
resolveSignatureEmission,
|
|
21
|
+
resolveWebhookConfig
|
|
22
|
+
} from "./chunk-V5VZPYMZ.js";
|
|
19
23
|
import {
|
|
20
24
|
CODEX_LISTEN_CAP_MS,
|
|
21
25
|
buildWebhookReceiverNote
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-X672ZN7V.js";
|
|
23
27
|
import {
|
|
24
28
|
secureFile
|
|
25
29
|
} from "./chunk-BLEGIR35.js";
|
|
26
|
-
import {
|
|
27
|
-
resolveWebhookConfig
|
|
28
|
-
} from "./chunk-F7L25L2J.js";
|
|
29
30
|
|
|
30
31
|
// src/wizard/wizard.ts
|
|
31
32
|
import crypto from "crypto";
|
|
@@ -57,28 +58,55 @@ function resolveWizardWebhook(opts) {
|
|
|
57
58
|
const url = (opts.webhookUrl ?? env["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
58
59
|
let secret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
59
60
|
if (url && !secret) secret = generateWebhookSecret();
|
|
61
|
+
const sigFormat = (opts.webhookSignatureFormat ?? env["KOJEE_WEBHOOK_SIGNATURE_FORMAT"] ?? "").trim();
|
|
62
|
+
const sigHeader = (opts.webhookSignatureHeader ?? env["KOJEE_WEBHOOK_SIGNATURE_HEADER"] ?? "").trim();
|
|
63
|
+
const sigPrefix = opts.webhookSignaturePrefix ?? env["KOJEE_WEBHOOK_SIGNATURE_PREFIX"];
|
|
64
|
+
const signatureEnv = [];
|
|
65
|
+
if (sigFormat) signatureEnv.push(["KOJEE_WEBHOOK_SIGNATURE_FORMAT", sigFormat]);
|
|
66
|
+
if (sigHeader) signatureEnv.push(["KOJEE_WEBHOOK_SIGNATURE_HEADER", sigHeader]);
|
|
67
|
+
if (sigPrefix !== void 0) signatureEnv.push(["KOJEE_WEBHOOK_SIGNATURE_PREFIX", sigPrefix]);
|
|
68
|
+
const emission = resolveSignatureEmission(Object.fromEntries(signatureEnv));
|
|
60
69
|
const resolution = resolveWebhookConfig({
|
|
61
70
|
KOJEE_WEBHOOK_URL: url,
|
|
62
71
|
KOJEE_WEBHOOK_SECRET: secret,
|
|
63
72
|
...env["KOJEE_WEBHOOK_TIMEOUT_MS"] !== void 0 ? { KOJEE_WEBHOOK_TIMEOUT_MS: env["KOJEE_WEBHOOK_TIMEOUT_MS"] } : {},
|
|
64
|
-
...env["KOJEE_WEBHOOK_MAX_RETRIES"] !== void 0 ? { KOJEE_WEBHOOK_MAX_RETRIES: env["KOJEE_WEBHOOK_MAX_RETRIES"] } : {}
|
|
73
|
+
...env["KOJEE_WEBHOOK_MAX_RETRIES"] !== void 0 ? { KOJEE_WEBHOOK_MAX_RETRIES: env["KOJEE_WEBHOOK_MAX_RETRIES"] } : {},
|
|
74
|
+
...Object.fromEntries(signatureEnv)
|
|
65
75
|
});
|
|
76
|
+
const warning = resolution.warning ?? (emission.warnings.length > 0 ? emission.warnings.join("; ") : void 0);
|
|
66
77
|
if (resolution.error) {
|
|
67
|
-
return {
|
|
78
|
+
return {
|
|
79
|
+
url,
|
|
80
|
+
secret,
|
|
81
|
+
redactedSummary: "",
|
|
82
|
+
signatureEnv,
|
|
83
|
+
signatureHeader: emission.header,
|
|
84
|
+
signaturePrefix: emission.prefix,
|
|
85
|
+
error: resolution.error
|
|
86
|
+
};
|
|
68
87
|
}
|
|
69
88
|
return {
|
|
70
89
|
url,
|
|
71
90
|
secret,
|
|
72
|
-
redactedSummary: resolution.config?.redactedSummary ?? `url=${url} secret=<redacted
|
|
91
|
+
redactedSummary: resolution.config?.redactedSummary ?? `url=${url} secret=<redacted>`,
|
|
92
|
+
signatureEnv,
|
|
93
|
+
signatureHeader: emission.header,
|
|
94
|
+
signaturePrefix: emission.prefix,
|
|
95
|
+
...warning !== void 0 ? { warning } : {}
|
|
73
96
|
};
|
|
74
97
|
}
|
|
75
|
-
function
|
|
98
|
+
function shellSingleQuote(value) {
|
|
99
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
100
|
+
}
|
|
101
|
+
function writeRuntimeEnvFile(runtime, url, secret, signatureEnv = []) {
|
|
76
102
|
const envPath = path.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
|
|
77
103
|
const body = [
|
|
78
104
|
`# kojee daemon env for runtime=${runtime} (source this before starting the daemon)`,
|
|
79
|
-
`export KOJEE_RUNTIME
|
|
80
|
-
`export KOJEE_WEBHOOK_URL
|
|
81
|
-
`export KOJEE_WEBHOOK_SECRET
|
|
105
|
+
`export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`,
|
|
106
|
+
`export KOJEE_WEBHOOK_URL=${shellSingleQuote(url)}`,
|
|
107
|
+
`export KOJEE_WEBHOOK_SECRET=${shellSingleQuote(secret)}`,
|
|
108
|
+
// Signature emission overrides (0.5.3) — only when explicitly configured.
|
|
109
|
+
...signatureEnv.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`),
|
|
82
110
|
""
|
|
83
111
|
].join("\n");
|
|
84
112
|
fs.mkdirSync(path.dirname(envPath), { recursive: true, mode: 448 });
|
|
@@ -156,7 +184,8 @@ function configureCodex(opts) {
|
|
|
156
184
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
157
185
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
158
186
|
webhookUrl: url,
|
|
159
|
-
webhookSecret: secret
|
|
187
|
+
webhookSecret: secret,
|
|
188
|
+
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {}
|
|
160
189
|
});
|
|
161
190
|
recordRuntime("codex");
|
|
162
191
|
const lines = [];
|
|
@@ -164,7 +193,12 @@ function configureCodex(opts) {
|
|
|
164
193
|
lines.push("Wake mode: webhook-sink + stop-hook peek (Codex has no channel injection).");
|
|
165
194
|
lines.push("");
|
|
166
195
|
lines.push("Wrote [mcp_servers.kojee] to ~/.codex/config.toml:");
|
|
167
|
-
lines.push(indent(buildCodexMcpServerTable({
|
|
196
|
+
lines.push(indent(buildCodexMcpServerTable({
|
|
197
|
+
webhookUrl: url,
|
|
198
|
+
webhookSecret: "<redacted>",
|
|
199
|
+
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {}
|
|
200
|
+
})));
|
|
201
|
+
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
168
202
|
lines.push("");
|
|
169
203
|
lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
|
|
170
204
|
lines.push(indent(buildCodexStopHookBlock()));
|
|
@@ -175,7 +209,7 @@ function configureCodex(opts) {
|
|
|
175
209
|
lines.push("Next steps (codex):");
|
|
176
210
|
lines.push(" Restart Codex (or start a new `codex` / `codex exec` session) to load the MCP server and hook.");
|
|
177
211
|
lines.push(" Stand up your webhook receiver at KOJEE_WEBHOOK_URL \u2014 contract:");
|
|
178
|
-
lines.push(indent(buildWebhookReceiverNote()));
|
|
212
|
+
lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
|
|
179
213
|
lines.push(" Verify: kojee-mcp doctor");
|
|
180
214
|
lines.push("");
|
|
181
215
|
lines.push(CODEX_UNVERIFIED_NOTE);
|
|
@@ -191,23 +225,26 @@ function configureWebhookDaemon(runtime, opts) {
|
|
|
191
225
|
lines.push(`Configured runtime: ${runtime}`);
|
|
192
226
|
lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
|
|
193
227
|
lines.push("");
|
|
228
|
+
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
194
229
|
if (wh.url) {
|
|
195
|
-
const envFile = writeRuntimeEnvFile(runtime, wh.url, wh.secret);
|
|
230
|
+
const envFile = writeRuntimeEnvFile(runtime, wh.url, wh.secret, wh.signatureEnv);
|
|
196
231
|
lines.push("Export these before starting the daemon (also written to a source-able file):");
|
|
197
|
-
lines.push(` export KOJEE_RUNTIME
|
|
198
|
-
lines.push(` export KOJEE_WEBHOOK_URL
|
|
232
|
+
lines.push(` export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`);
|
|
233
|
+
lines.push(` export KOJEE_WEBHOOK_URL=${shellSingleQuote(wh.url)}`);
|
|
199
234
|
lines.push(` export KOJEE_WEBHOOK_SECRET=<generated; in ${envFile}>`);
|
|
235
|
+
for (const [k, v] of wh.signatureEnv) lines.push(` export ${k}=${shellSingleQuote(v)}`);
|
|
200
236
|
lines.push(` (validated: ${wh.redactedSummary})`);
|
|
201
237
|
lines.push(` source ${envFile}`);
|
|
202
238
|
} else {
|
|
203
239
|
lines.push("Set the daemon env (no receiver URL supplied yet):");
|
|
204
|
-
lines.push(` export KOJEE_RUNTIME
|
|
240
|
+
lines.push(` export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`);
|
|
205
241
|
lines.push(` export KOJEE_WEBHOOK_URL="https://YOUR-RECEIVER.local/kojee"`);
|
|
206
242
|
lines.push(` export KOJEE_WEBHOOK_SECRET=<generate a hex secret>`);
|
|
243
|
+
for (const [k, v] of wh.signatureEnv) lines.push(` export ${k}=${shellSingleQuote(v)}`);
|
|
207
244
|
}
|
|
208
245
|
lines.push("");
|
|
209
246
|
lines.push("Receiver contract:");
|
|
210
|
-
lines.push(indent(buildWebhookReceiverNote()));
|
|
247
|
+
lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
|
|
211
248
|
lines.push("");
|
|
212
249
|
lines.push(`Next steps (${runtime}):`);
|
|
213
250
|
lines.push(" Start the daemon with the env above. Verify: kojee-mcp doctor (after the daemon is up).");
|
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kojee-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./lib": {
|
|
9
|
+
"types": "./dist/lib.d.ts",
|
|
10
|
+
"default": "./dist/lib.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
6
14
|
"bin": {
|
|
7
15
|
"kojee-mcp": "dist/cli.js"
|
|
8
16
|
},
|
|
9
17
|
"_buildNote": "src/version.ts resolves package.json via '../package.json' from import.meta.url; this assumes dist output stays flat one level under the package root (tsup default outDir=dist, no nesting). If the build adds outDir nesting or a deeper entry, update version.ts's relative path.",
|
|
10
18
|
"scripts": {
|
|
11
|
-
"build": "tsup src/cli.ts src/index.ts --format esm --dts --clean",
|
|
19
|
+
"build": "tsup src/cli.ts src/index.ts src/lib.ts --format esm --dts --clean",
|
|
20
|
+
"prepublishOnly": "npm run build && node scripts/verify-tarball.mjs",
|
|
12
21
|
"dev": "tsup src/cli.ts --format esm --watch",
|
|
13
22
|
"typecheck": "tsc --noEmit",
|
|
14
23
|
"test": "vitest run",
|
package/dist/chunk-F7L25L2J.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// src/tandem/webhook-config.ts
|
|
2
|
-
var WEBHOOK_DEFAULT_TIMEOUT_MS = 5e3;
|
|
3
|
-
var WEBHOOK_DEFAULT_MAX_RETRIES = 4;
|
|
4
|
-
function redactUrlUserinfo(rawUrl, parsed) {
|
|
5
|
-
if (!parsed.username && !parsed.password) return rawUrl;
|
|
6
|
-
parsed.username = "";
|
|
7
|
-
parsed.password = "";
|
|
8
|
-
return parsed.toString();
|
|
9
|
-
}
|
|
10
|
-
function parsePositiveInt(raw, fallback) {
|
|
11
|
-
if (raw === void 0) return fallback;
|
|
12
|
-
const n = Number(raw);
|
|
13
|
-
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return fallback;
|
|
14
|
-
return n;
|
|
15
|
-
}
|
|
16
|
-
function resolveWebhookConfig(env = process.env) {
|
|
17
|
-
const url = (env["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
18
|
-
if (!url) {
|
|
19
|
-
return { enabled: false, config: null };
|
|
20
|
-
}
|
|
21
|
-
let parsed;
|
|
22
|
-
try {
|
|
23
|
-
parsed = new URL(url);
|
|
24
|
-
} catch {
|
|
25
|
-
return {
|
|
26
|
-
enabled: false,
|
|
27
|
-
config: null,
|
|
28
|
-
error: `KOJEE_WEBHOOK_URL is not a valid URL \u2014 webhook sink DISABLED`
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
32
|
-
return {
|
|
33
|
-
enabled: false,
|
|
34
|
-
config: null,
|
|
35
|
-
error: `KOJEE_WEBHOOK_URL must be http(s) (got ${parsed.protocol}) \u2014 webhook sink DISABLED`
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
const secret = (env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
39
|
-
if (!secret) {
|
|
40
|
-
return {
|
|
41
|
-
enabled: false,
|
|
42
|
-
config: null,
|
|
43
|
-
error: "KOJEE_WEBHOOK_URL is set but KOJEE_WEBHOOK_SECRET is missing \u2014 webhook sink DISABLED (the proxy NEVER sends unsigned webhooks)"
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const timeoutMs = parsePositiveInt(env["KOJEE_WEBHOOK_TIMEOUT_MS"], WEBHOOK_DEFAULT_TIMEOUT_MS);
|
|
47
|
-
const maxRetries = parsePositiveInt(env["KOJEE_WEBHOOK_MAX_RETRIES"], WEBHOOK_DEFAULT_MAX_RETRIES);
|
|
48
|
-
const safeUrl = redactUrlUserinfo(url, parsed);
|
|
49
|
-
const redactedSummary = `url=${safeUrl} secret=<redacted> timeoutMs=${timeoutMs} maxRetries=${maxRetries}`;
|
|
50
|
-
return {
|
|
51
|
-
enabled: true,
|
|
52
|
-
config: { url, secret, timeoutMs, maxRetries, redactedSummary }
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export {
|
|
57
|
-
WEBHOOK_DEFAULT_TIMEOUT_MS,
|
|
58
|
-
WEBHOOK_DEFAULT_MAX_RETRIES,
|
|
59
|
-
resolveWebhookConfig
|
|
60
|
-
};
|