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.
Files changed (37) hide show
  1. package/README.md +112 -5
  2. package/dist/{chunk-YEC7IHIG.js → chunk-2BDAM3TH.js} +92 -523
  3. package/dist/chunk-2MIISF2W.js +35 -0
  4. package/dist/chunk-3XDJOHMZ.js +223 -0
  5. package/dist/{chunk-ZW4SW7LJ.js → chunk-64EOLZNI.js} +14 -5
  6. package/dist/chunk-6SK6ITFE.js +142 -0
  7. package/dist/chunk-GI2CKKBL.js +46 -0
  8. package/dist/chunk-HIZ4NDWN.js +141 -0
  9. package/dist/chunk-LDZXU3DW.js +170 -0
  10. package/dist/{resubscribe-SLZNA76S.js → chunk-OT2GILXC.js} +1 -0
  11. package/dist/{chunk-WBMX4CHB.js → chunk-UEGQGXPY.js} +57 -40
  12. package/dist/chunk-V5VZPYMZ.js +185 -0
  13. package/dist/{chunk-C6GZ2L2W.js → chunk-X672ZN7V.js} +5 -2
  14. package/dist/cli.js +47 -24
  15. package/dist/{codex-stop-hook-JOTBCS5K.js → codex-stop-hook-SWA53ECG.js} +1 -1
  16. package/dist/control-token-4BUCTYQB.js +13 -0
  17. package/dist/{doctor-TSHOMT5X.js → doctor-QCQDFLEH.js} +30 -17
  18. package/dist/{doctor-codex-BMI5JOO6.js → doctor-codex-NZ53ROQA.js} +12 -5
  19. package/dist/ensure-join-7AEDJMPE.js +96 -0
  20. package/dist/gateway-client-93P1E0CZ.d.ts +92 -0
  21. package/dist/{hook-server-QF5JVUHV.js → hook-server-37E2LUKJ.js} +91 -0
  22. package/dist/index.d.ts +18 -15
  23. package/dist/index.js +9 -3
  24. package/dist/lib.d.ts +427 -0
  25. package/dist/lib.js +44 -0
  26. package/dist/reconnect-scheduler-JSXCJKQP.js +26 -0
  27. package/dist/resubscribe-G5OGDZJD.js +6 -0
  28. package/dist/send-cli-C2F4WTBN.js +72 -0
  29. package/dist/{stop-hook-SEPWWETV.js → stop-hook-TRAMQYNE.js} +16 -8
  30. package/dist/{tail-stream-BYKO4DW6.js → tail-stream-VUZBYKXS.js} +4 -3
  31. package/dist/{user-prompt-submit-hook-ARPEO6FF.js → user-prompt-submit-hook-ZD2XKN7U.js} +7 -1
  32. package/dist/webhook-config-O4WMQ532.js +20 -0
  33. package/dist/{webhook-sink-7OYZBWXA.js → webhook-sink-NWGCUDGY.js} +28 -5
  34. package/dist/{wizard-7KHD5JT4.js → wizard-OSOAY4GO.js} +64 -27
  35. package/package.json +11 -2
  36. package/dist/chunk-F7L25L2J.js +0 -60
  37. 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
- const timer = setTimeout(() => controller.abort(), config.timeoutMs);
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 re-verifies.
67
- "X-Kojee-Signature": item.signature,
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-ZW4SW7LJ.js";
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
- clearRuntimeRecord,
13
- readRecordedRuntime,
14
- recordRuntime
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-C6GZ2L2W.js";
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 { url, secret, redactedSummary: "", error: resolution.error };
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 writeRuntimeEnvFile(runtime, url, secret) {
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="${runtime}"`,
80
- `export KOJEE_WEBHOOK_URL="${url}"`,
81
- `export KOJEE_WEBHOOK_SECRET="${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({ webhookUrl: url, webhookSecret: "<redacted>" })));
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="${runtime}"`);
198
- lines.push(` export KOJEE_WEBHOOK_URL="${wh.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="${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",
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",
@@ -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
- };
@@ -1,10 +0,0 @@
1
- import {
2
- WEBHOOK_DEFAULT_MAX_RETRIES,
3
- WEBHOOK_DEFAULT_TIMEOUT_MS,
4
- resolveWebhookConfig
5
- } from "./chunk-F7L25L2J.js";
6
- export {
7
- WEBHOOK_DEFAULT_MAX_RETRIES,
8
- WEBHOOK_DEFAULT_TIMEOUT_MS,
9
- resolveWebhookConfig
10
- };