kojee-mcp 0.5.3 → 0.5.4
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 +88 -0
- package/dist/chunk-2MIISF2W.js +35 -0
- package/dist/chunk-36L3GCU3.js +213 -0
- package/dist/{chunk-YEC7IHIG.js → chunk-62KH6VNQ.js} +49 -381
- package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
- package/dist/chunk-HIZ4NDWN.js +141 -0
- package/dist/chunk-LDZXU3DW.js +170 -0
- package/dist/chunk-OSKHA5DS.js +185 -0
- package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
- package/dist/{chunk-WBMX4CHB.js → chunk-YVUXQ4Z2.js} +4 -32
- package/dist/cli.js +37 -9
- package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
- package/dist/control-token-TYDAL477.js +35 -0
- package/dist/{doctor-TSHOMT5X.js → doctor-TXWMMSRC.js} +2 -2
- package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-3A7KYOVX.js} +10 -3
- package/dist/{hook-server-QF5JVUHV.js → hook-server-NDJSV22J.js} +85 -0
- package/dist/index.js +6 -3
- package/dist/send-cli-7QJ36YY7.js +72 -0
- package/dist/{stop-hook-SEPWWETV.js → stop-hook-GO363SMD.js} +1 -1
- package/dist/{tail-stream-BYKO4DW6.js → tail-stream-U436QL2X.js} +2 -1
- package/dist/webhook-config-UKUSI2FE.js +20 -0
- package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-GCLL2S6S.js} +12 -3
- package/dist/{wizard-7KHD5JT4.js → wizard-Z5JA3YPV.js} +63 -26
- package/package.json +1 -1
- package/dist/chunk-F7L25L2J.js +0 -60
- package/dist/webhook-config-5TLLX7RA.js +0 -10
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
secureDir,
|
|
3
|
+
secureFile
|
|
4
|
+
} from "./chunk-BLEGIR35.js";
|
|
5
|
+
|
|
6
|
+
// src/tandem/control-token.ts
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import path from "path";
|
|
11
|
+
function controlTokenPath(kojeeDir = path.join(os.homedir(), ".kojee")) {
|
|
12
|
+
return path.join(kojeeDir, "control-token");
|
|
13
|
+
}
|
|
14
|
+
function issueControlToken(filePath = controlTokenPath()) {
|
|
15
|
+
const token = crypto.randomBytes(32).toString("hex");
|
|
16
|
+
const dir = path.dirname(filePath);
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
18
|
+
secureDir(dir);
|
|
19
|
+
fs.writeFileSync(filePath, token + "\n", { mode: 384 });
|
|
20
|
+
secureFile(filePath);
|
|
21
|
+
return token;
|
|
22
|
+
}
|
|
23
|
+
function loadControlToken(filePath = controlTokenPath()) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(filePath, "utf8").trim();
|
|
26
|
+
return raw.length > 0 ? raw : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
controlTokenPath,
|
|
33
|
+
issueControlToken,
|
|
34
|
+
loadControlToken
|
|
35
|
+
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
buildMonitorSpawn,
|
|
10
10
|
buildReplyRecipe
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-X672ZN7V.js";
|
|
12
12
|
import {
|
|
13
13
|
monitorHeartbeatPath,
|
|
14
14
|
statusLogPath
|
|
@@ -220,7 +220,7 @@ function formatDoctorReport(report) {
|
|
|
220
220
|
async function runDoctor() {
|
|
221
221
|
const { readRecordedRuntime } = await import("./runtime-record-WO4IECM6.js");
|
|
222
222
|
if (readRecordedRuntime() === "codex") {
|
|
223
|
-
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-
|
|
223
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-3A7KYOVX.js");
|
|
224
224
|
const report2 = collectCodexDoctorReport();
|
|
225
225
|
console.error(formatCodexDoctorReport(report2));
|
|
226
226
|
return report2.verdict === "broken" ? 1 : 0;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultCodexConfigPath,
|
|
3
3
|
defaultCodexHooksPath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-64EOLZNI.js";
|
|
5
5
|
import "./chunk-SQL56SEB.js";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_LISTEN_CAP_MS
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X672ZN7V.js";
|
|
9
9
|
import "./chunk-BLEGIR35.js";
|
|
10
10
|
import {
|
|
11
11
|
resolveWebhookConfig
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OSKHA5DS.js";
|
|
13
13
|
|
|
14
14
|
// src/doctor-codex.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -84,6 +84,13 @@ function collectCodexDoctorReport(deps = {}) {
|
|
|
84
84
|
ok: true,
|
|
85
85
|
detail: `enabled \u2014 ${resolution.config.redactedSummary}`
|
|
86
86
|
});
|
|
87
|
+
if (resolution.warning) {
|
|
88
|
+
checks.push({
|
|
89
|
+
name: "webhook signature config",
|
|
90
|
+
ok: "warn",
|
|
91
|
+
detail: `${resolution.warning} \u2014 ${WIZARD_RERUN} with valid signature flags`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
87
94
|
} else if (resolution.error) {
|
|
88
95
|
checks.push({
|
|
89
96
|
name: "webhook sink",
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeSend,
|
|
3
|
+
httpStatusForEnvelope,
|
|
4
|
+
parseSendRequest,
|
|
5
|
+
sendFailure
|
|
6
|
+
} from "./chunk-HIZ4NDWN.js";
|
|
7
|
+
import "./chunk-LDZXU3DW.js";
|
|
8
|
+
|
|
1
9
|
// src/tandem/hook-server.ts
|
|
10
|
+
import crypto from "crypto";
|
|
2
11
|
import { createServer } from "http";
|
|
3
12
|
async function startHookServer(opts) {
|
|
4
13
|
const server = createServer((req, res) => {
|
|
@@ -38,8 +47,84 @@ async function handleRequest(req, res, opts) {
|
|
|
38
47
|
}
|
|
39
48
|
return json(res, 400, { error: "unknown type", detail: "type must be 'stop' or 'user-prompt-submit'" });
|
|
40
49
|
}
|
|
50
|
+
if (req.method === "POST" && url.pathname === "/send") {
|
|
51
|
+
return handleSend(req, res, opts);
|
|
52
|
+
}
|
|
41
53
|
return json(res, 404, { error: "not_found", detail: `${req.method} ${url.pathname}` });
|
|
42
54
|
}
|
|
55
|
+
var MAX_SEND_BODY_BYTES = 256 * 1024;
|
|
56
|
+
async function handleSend(req, res, opts) {
|
|
57
|
+
if (!opts.send) {
|
|
58
|
+
return respondEnvelope(
|
|
59
|
+
res,
|
|
60
|
+
sendFailure(
|
|
61
|
+
"send_unavailable",
|
|
62
|
+
"this daemon started without a send surface (no gateway/control token wired)"
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (!bearerMatches(req.headers.authorization, opts.send.authToken)) {
|
|
67
|
+
return respondEnvelope(
|
|
68
|
+
res,
|
|
69
|
+
sendFailure(
|
|
70
|
+
"unauthorized",
|
|
71
|
+
"missing or invalid bearer \u2014 read the control token from the file named by the discovery file's controlTokenPath and send it as `Authorization: Bearer <token>`"
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
let raw;
|
|
76
|
+
try {
|
|
77
|
+
raw = await readBody(req, MAX_SEND_BODY_BYTES);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return respondEnvelope(
|
|
80
|
+
res,
|
|
81
|
+
sendFailure("bad_request", err.message)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
let input;
|
|
85
|
+
try {
|
|
86
|
+
input = JSON.parse(raw);
|
|
87
|
+
} catch {
|
|
88
|
+
return respondEnvelope(
|
|
89
|
+
res,
|
|
90
|
+
sendFailure("bad_request", "request body must be valid JSON")
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const parsed = parseSendRequest(input);
|
|
94
|
+
if (!parsed.ok) {
|
|
95
|
+
return respondEnvelope(res, parsed);
|
|
96
|
+
}
|
|
97
|
+
const envelope = await executeSend(opts.send.gateway, parsed.request);
|
|
98
|
+
respondEnvelope(res, envelope);
|
|
99
|
+
}
|
|
100
|
+
function respondEnvelope(res, envelope) {
|
|
101
|
+
json(res, httpStatusForEnvelope(envelope), envelope);
|
|
102
|
+
}
|
|
103
|
+
function bearerMatches(header, expected) {
|
|
104
|
+
if (!header || !header.startsWith("Bearer ")) return false;
|
|
105
|
+
const presented = header.slice("Bearer ".length).trim();
|
|
106
|
+
if (presented.length === 0) return false;
|
|
107
|
+
const a = crypto.createHash("sha256").update(presented).digest();
|
|
108
|
+
const b = crypto.createHash("sha256").update(expected).digest();
|
|
109
|
+
return crypto.timingSafeEqual(a, b);
|
|
110
|
+
}
|
|
111
|
+
function readBody(req, maxBytes) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const chunks = [];
|
|
114
|
+
let total = 0;
|
|
115
|
+
req.on("data", (chunk) => {
|
|
116
|
+
total += chunk.length;
|
|
117
|
+
if (total > maxBytes) {
|
|
118
|
+
reject(new Error(`request body exceeds ${maxBytes} bytes`));
|
|
119
|
+
req.destroy();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
chunks.push(chunk);
|
|
123
|
+
});
|
|
124
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
125
|
+
req.on("error", reject);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
43
128
|
function respondWithEvents(res, opts) {
|
|
44
129
|
const entries = opts.queue.takeForHook();
|
|
45
130
|
const events = entries.map((entry) => opts.adapter.formatTandemEvent(entry.event));
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startProxy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-62KH6VNQ.js";
|
|
4
|
+
import "./chunk-YVUXQ4Z2.js";
|
|
4
5
|
import "./chunk-BJMASMKX.js";
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-X672ZN7V.js";
|
|
7
|
+
import "./chunk-36L3GCU3.js";
|
|
8
|
+
import "./chunk-2MIISF2W.js";
|
|
9
|
+
import "./chunk-LDZXU3DW.js";
|
|
7
10
|
import "./chunk-BLEGIR35.js";
|
|
8
11
|
export {
|
|
9
12
|
startProxy
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadPairedConfig
|
|
3
|
+
} from "./chunk-YH27B6SW.js";
|
|
4
|
+
import {
|
|
5
|
+
GatewayClient,
|
|
6
|
+
loadKeystore
|
|
7
|
+
} from "./chunk-36L3GCU3.js";
|
|
8
|
+
import "./chunk-2MIISF2W.js";
|
|
9
|
+
import {
|
|
10
|
+
executeSend,
|
|
11
|
+
parseSendRequest,
|
|
12
|
+
sendFailure
|
|
13
|
+
} from "./chunk-HIZ4NDWN.js";
|
|
14
|
+
import "./chunk-LDZXU3DW.js";
|
|
15
|
+
import "./chunk-BLEGIR35.js";
|
|
16
|
+
|
|
17
|
+
// src/tandem/send-cli.ts
|
|
18
|
+
import os from "os";
|
|
19
|
+
import path from "path";
|
|
20
|
+
async function createPairedGateway(kojeeDir) {
|
|
21
|
+
const configPath = path.join(kojeeDir, "config.json");
|
|
22
|
+
const paired = loadPairedConfig(configPath);
|
|
23
|
+
if (!paired) {
|
|
24
|
+
return sendFailure(
|
|
25
|
+
"not_paired",
|
|
26
|
+
`no paired config at ${configPath} \u2014 run \`kojee-mcp pair <code> --url <broker>\` first`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const brokerUrl = paired.broker_url.replace(/\/+$/, "");
|
|
30
|
+
const keystorePath = path.join(kojeeDir, "keypair.json");
|
|
31
|
+
const keystore = await loadKeystore(keystorePath, brokerUrl).catch(() => null);
|
|
32
|
+
if (!keystore) {
|
|
33
|
+
return sendFailure(
|
|
34
|
+
"not_enrolled",
|
|
35
|
+
`no DPoP keystore for ${brokerUrl} at ${keystorePath} \u2014 start the proxy once (or re-run \`kojee-mcp pair\`) to enroll a keypair`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const sessionId = GatewayClient.deriveSessionId(paired.token);
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
gateway: new GatewayClient(
|
|
42
|
+
brokerUrl,
|
|
43
|
+
paired.token,
|
|
44
|
+
keystore.privateKey,
|
|
45
|
+
keystore.kid,
|
|
46
|
+
sessionId
|
|
47
|
+
)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function runSendCli(args, deps = {}) {
|
|
51
|
+
const parsed = parseSendRequest({
|
|
52
|
+
tandem_id: args.tandemId,
|
|
53
|
+
body: args.body,
|
|
54
|
+
...args.replyTo !== void 0 ? { reply_to: args.replyTo } : {},
|
|
55
|
+
...args.kind !== void 0 ? { kind: args.kind } : {}
|
|
56
|
+
});
|
|
57
|
+
if (!parsed.ok) {
|
|
58
|
+
return { exitCode: 1, envelope: parsed };
|
|
59
|
+
}
|
|
60
|
+
const kojeeDir = args.kojeeDir ?? path.join(os.homedir(), ".kojee");
|
|
61
|
+
const createGateway = deps.createGateway ?? createPairedGateway;
|
|
62
|
+
const resolution = await createGateway(kojeeDir);
|
|
63
|
+
if (!resolution.ok) {
|
|
64
|
+
return { exitCode: 1, envelope: resolution };
|
|
65
|
+
}
|
|
66
|
+
const envelope = await executeSend(resolution.gateway, parsed.request);
|
|
67
|
+
return { exitCode: envelope.ok ? 0 : 1, envelope };
|
|
68
|
+
}
|
|
69
|
+
export {
|
|
70
|
+
createPairedGateway,
|
|
71
|
+
runSendCli
|
|
72
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WEBHOOK_DEFAULT_MAX_RETRIES,
|
|
3
|
+
WEBHOOK_DEFAULT_SIGNATURE_HEADER,
|
|
4
|
+
WEBHOOK_DEFAULT_SIGNATURE_PREFIX,
|
|
5
|
+
WEBHOOK_DEFAULT_TIMEOUT_MS,
|
|
6
|
+
WEBHOOK_SIGNATURE_FORMATS,
|
|
7
|
+
emissionRejectionReason,
|
|
8
|
+
resolveSignatureEmission,
|
|
9
|
+
resolveWebhookConfig
|
|
10
|
+
} from "./chunk-OSKHA5DS.js";
|
|
11
|
+
export {
|
|
12
|
+
WEBHOOK_DEFAULT_MAX_RETRIES,
|
|
13
|
+
WEBHOOK_DEFAULT_SIGNATURE_HEADER,
|
|
14
|
+
WEBHOOK_DEFAULT_SIGNATURE_PREFIX,
|
|
15
|
+
WEBHOOK_DEFAULT_TIMEOUT_MS,
|
|
16
|
+
WEBHOOK_SIGNATURE_FORMATS,
|
|
17
|
+
emissionRejectionReason,
|
|
18
|
+
resolveSignatureEmission,
|
|
19
|
+
resolveWebhookConfig
|
|
20
|
+
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WEBHOOK_DEFAULT_SIGNATURE_HEADER,
|
|
3
|
+
WEBHOOK_DEFAULT_SIGNATURE_PREFIX
|
|
4
|
+
} from "./chunk-OSKHA5DS.js";
|
|
5
|
+
|
|
1
6
|
// src/tandem/webhook-sink.ts
|
|
2
7
|
import crypto from "crypto";
|
|
3
8
|
import { ulid } from "ulidx";
|
|
@@ -26,6 +31,8 @@ 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;
|
|
@@ -51,7 +58,7 @@ function createWebhookSink(config, options = {}) {
|
|
|
51
58
|
}
|
|
52
59
|
function prepare(event) {
|
|
53
60
|
const body = JSON.stringify(event);
|
|
54
|
-
const signature = computeWebhookSignature(config.secret, body);
|
|
61
|
+
const signature = signaturePrefix + computeWebhookSignature(config.secret, body);
|
|
55
62
|
const deliveryId = event.id || ulid();
|
|
56
63
|
return { event, body, signature, deliveryId };
|
|
57
64
|
}
|
|
@@ -63,8 +70,10 @@ function createWebhookSink(config, options = {}) {
|
|
|
63
70
|
method: "POST",
|
|
64
71
|
headers: {
|
|
65
72
|
"Content-Type": "application/json",
|
|
66
|
-
// hex SHA-256 HMAC of the RAW body bytes — the receiver
|
|
67
|
-
|
|
73
|
+
// (prefix +) hex SHA-256 HMAC of the RAW body bytes — the receiver
|
|
74
|
+
// re-verifies. Header name defaults to X-Kojee-Signature; the
|
|
75
|
+
// GitHub-convention preset emits X-Hub-Signature-256 / sha256=.
|
|
76
|
+
[signatureHeader]: item.signature,
|
|
68
77
|
"X-Kojee-Delivery": item.deliveryId
|
|
69
78
|
},
|
|
70
79
|
body: item.body,
|
|
@@ -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
|
-
import {
|
|
12
|
-
clearRuntimeRecord,
|
|
13
|
-
readRecordedRuntime,
|
|
14
|
-
recordRuntime
|
|
15
|
-
} from "./chunk-EW72ZNQL.js";
|
|
16
|
-
import {
|
|
17
|
-
kojeeHomeDir
|
|
18
|
-
} from "./chunk-SQL56SEB.js";
|
|
19
19
|
import {
|
|
20
20
|
CODEX_LISTEN_CAP_MS,
|
|
21
21
|
buildWebhookReceiverNote
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-X672ZN7V.js";
|
|
23
23
|
import {
|
|
24
24
|
secureFile
|
|
25
25
|
} from "./chunk-BLEGIR35.js";
|
|
26
26
|
import {
|
|
27
|
+
resolveSignatureEmission,
|
|
27
28
|
resolveWebhookConfig
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-OSKHA5DS.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
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
|
-
};
|