kojee-mcp 0.5.19 → 0.6.0

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.
@@ -2,10 +2,10 @@ import {
2
2
  buildCatchUpNote,
3
3
  buildMonitorSpawn,
4
4
  buildReplyRecipe
5
- } from "./chunk-X672ZN7V.js";
5
+ } from "./chunk-Q7T3WAST.js";
6
6
  import {
7
7
  translateToolCallResult
8
- } from "./chunk-2OLXXOT3.js";
8
+ } from "./chunk-PPTKGWFF.js";
9
9
 
10
10
  // src/server.ts
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  claudeCodeAdapter
3
- } from "./chunk-XXFVWP6H.js";
3
+ } from "./chunk-N35X22G7.js";
4
4
  import {
5
5
  GatewayClient,
6
6
  applyStableSessionId
7
- } from "./chunk-U4MNICQI.js";
7
+ } from "./chunk-KH5YEJIM.js";
8
8
  import {
9
9
  AuthModule
10
10
  } from "./chunk-JXMVZEQ7.js";
11
11
  import {
12
12
  createMcpServer,
13
13
  startMcpServer
14
- } from "./chunk-XIDCYUTK.js";
14
+ } from "./chunk-ALZX3SGI.js";
15
15
  import {
16
16
  findClaudeAncestorPid
17
17
  } from "./chunk-VHKPWUX7.js";
@@ -238,7 +238,7 @@ async function startProxy(config) {
238
238
  }
239
239
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
240
240
  let server;
241
- const { selectDelivery } = await import("./registry-CGLE5X54.js");
241
+ const { selectDelivery } = await import("./registry-YLOFZDI2.js");
242
242
  const delivery = selectDelivery(adapter.runtime, {
243
243
  supportsChannels: adapter.supportsChannels
244
244
  });
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  buildCodexWakeReason
6
- } from "./chunk-X672ZN7V.js";
6
+ } from "./chunk-Q7T3WAST.js";
7
7
 
8
8
  // src/hooks/codex-stop-hook.ts
9
9
  import fs from "fs";
@@ -8,7 +8,7 @@ import {
8
8
  translateHttpError,
9
9
  translateJsonRpcError,
10
10
  translateNetworkError
11
- } from "./chunk-2OLXXOT3.js";
11
+ } from "./chunk-PPTKGWFF.js";
12
12
 
13
13
  // src/gateway-client.ts
14
14
  import crypto from "crypto";
@@ -104,8 +104,11 @@ var GatewayClient = class {
104
104
  const body = await this.tryParseErrorBody(response);
105
105
  const translated = translateHttpError(response.status, body?.error);
106
106
  if (translated) return translated;
107
+ const detail = body?.error_description ?? body?.detail ?? body?.error ?? "<no detail>";
107
108
  return {
108
- content: [{ type: "text", text: `Gateway error: ${response.status}` }],
109
+ content: [
110
+ { type: "text", text: `Gateway error ${response.status}: ${detail}` }
111
+ ],
109
112
  isError: true
110
113
  };
111
114
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildReplyRecipe
3
- } from "./chunk-X672ZN7V.js";
3
+ } from "./chunk-Q7T3WAST.js";
4
4
 
5
5
  // src/adapters/claude-code.ts
6
6
  function computeSeverity(event) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  translateToolCallResult
3
- } from "./chunk-2OLXXOT3.js";
3
+ } from "./chunk-PPTKGWFF.js";
4
4
 
5
5
  // src/tandem/send.ts
6
6
  var SEND_KINDS = ["message", "status"];
@@ -33,7 +33,7 @@ function translateHttpError(status, errorCode, trigger) {
33
33
  }
34
34
  if (errorCode === "invalid_dpop_proof") {
35
35
  return makeError(
36
- "Authentication failed. The proxy will attempt to re-enroll. If this persists, regenerate your gateway token."
36
+ "Authentication failed (DPoP key rejected). Restart the proxy so it re-enrolls its key. If this persists, regenerate your gateway token."
37
37
  );
38
38
  }
39
39
  if (errorCode === "key_enrollment_required") {
@@ -61,8 +61,71 @@ function translateHttpError(status, errorCode, trigger) {
61
61
  }
62
62
  return null;
63
63
  }
64
+ function translateTandemNotActive(reason) {
65
+ switch (reason) {
66
+ // --- genuine inactive / reaped seat: re-joining is the right fix ---
67
+ case "not_a_member":
68
+ case "not_active_member":
69
+ return "You aren't an active member of that Tandem (the seat may have been reaped). Run tandem_join to reactivate it, then retry.";
70
+ // --- authentication: pairing problem, tandem_join won't help ---
71
+ case "unauthenticated":
72
+ return "Your proxy session isn't authenticated/paired with Kojee, so this call was rejected. Re-pair the proxy \u2014 tandem_join won't fix this.";
73
+ // --- existence / visibility ---
74
+ case "tandem_not_found":
75
+ return "That Tandem doesn't exist or isn't visible to you.";
76
+ // --- power / scope: you lack the required role ---
77
+ case "owner_only":
78
+ return "This action requires owner power in this Tandem, which you don't have.";
79
+ case "admin_only":
80
+ return "This action requires admin power in this Tandem, which you don't have.";
81
+ // --- capacity ---
82
+ case "member_cap_reached":
83
+ return "This Tandem is full (member cap reached); rejoining won't help \u2014 ask an admin to make room.";
84
+ // --- message-scoped ---
85
+ case "message_not_found":
86
+ return "That message doesn't exist in this Tandem (wrong id, or it was already redacted).";
87
+ case "redact_forbidden":
88
+ return "You can't redact that message \u2014 you're neither its author nor an admin of this Tandem.";
89
+ // --- targeting ---
90
+ case "target_principal_not_found":
91
+ return "The target principal isn't a member of this Tandem.";
92
+ // --- invitations ---
93
+ case "invitation_not_found":
94
+ return "That invitation doesn't exist, or it was already revoked or used.";
95
+ case "invite_required":
96
+ return "This Tandem requires an invite to join; ask an owner or admin to invite you.";
97
+ case "invite_invalid":
98
+ return "That invite token is invalid.";
99
+ case "invite_expired":
100
+ return "That invite has expired; request a fresh one.";
101
+ case "invite_exhausted":
102
+ return "That invite has no remaining uses; request a fresh one.";
103
+ case "invite_wrong_principal":
104
+ return "That invite was issued to a different principal and can't be used by you.";
105
+ // --- join requests / knocks ---
106
+ case "join_request_not_found":
107
+ return "That join request doesn't exist or was already decided.";
108
+ case "join_request_not_pending":
109
+ return "That join request was already decided (it's no longer pending).";
110
+ case "already_member":
111
+ return "You're already a member of that Tandem.";
112
+ case "already_requested":
113
+ return "You already have a pending join request for that Tandem.";
114
+ case "knock_required":
115
+ return "This Tandem requires a knock; request to join and wait for an admin to approve.";
116
+ case "knock_cooldown":
117
+ return "You're knocking too frequently; wait a bit before requesting to join again.";
118
+ // --- direct (1:1) Tandems ---
119
+ case "direct_tandem_immutable_membership":
120
+ case "direct_immutable":
121
+ return "This is a direct (1:1) Tandem; its membership can't be changed.";
122
+ // --- unknown token: surface the real reason verbatim, never the reaped string ---
123
+ default:
124
+ return reason ? `Tandem request refused: ${reason}.` : "Tandem request refused.";
125
+ }
126
+ }
64
127
  var TANDEM_ERROR_MESSAGES = {
65
- [-32001]: () => "You aren't an active member of that Tandem (the seat may have been reaped). Run tandem_join to reactivate it, then retry.",
128
+ [-32001]: (_data, message) => translateTandemNotActive((message ?? "").trim()),
66
129
  [-32003]: () => "This Tandem is hardened to owner-only membership; you can't join.",
67
130
  [-32004]: () => "You aren't a member of that Tandem. Use tandem_join(join_link) first.",
68
131
  [-32006]: (data) => {
@@ -91,7 +154,10 @@ function translateJsonRpcError(error) {
91
154
  content: [
92
155
  {
93
156
  type: "text",
94
- text: tandemMessage(error.data)
157
+ text: tandemMessage(
158
+ error.data,
159
+ msg
160
+ )
95
161
  }
96
162
  ],
97
163
  isError: true
@@ -13,7 +13,7 @@ function buildReplyRecipe(event) {
13
13
  return `reply directly with tandem_send(tandem_id, ${SEND_BODY_PARAM}, reply_to=<msg>); fetch context only if needed via tandem_messages(tandem_id, since=cursor-1)`;
14
14
  }
15
15
  function buildCatchUpNote() {
16
- return "If the event line `cursor=<n>` ever jumps (a gap) or the log resets (the proxy caps and truncates the messages log in place on overflow \u2014 a `status=rotated` line is recorded in the status sibling), you may have missed messages: catch up with tandem_messages(tandem_id, since=<last cursor you saw>).";
16
+ return "Cursors are allocated per-Tandem, and the event-log interleaves every subscribed room into one stream \u2014 so track the last `cursor=<n>` you saw keyed by the line's `tandem=<id>` field, and only treat a jump WITHIN the same tandem_id as a gap (a jump between lines from different rooms is normal interleaving, not a missed message). If the cursor jumps within one tandem, or the log resets (the proxy caps and truncates the messages log in place on overflow \u2014 a `status=rotated` line is recorded in the status sibling), you may have missed messages: catch up with tandem_messages(tandem_id, since=<last cursor you saw for THAT tandem>).";
17
17
  }
18
18
  function buildMonitorNudge(logPath) {
19
19
  return `[kojee] Tandem events are being logged but no Monitor is reading them \u2014 you may be missing wake notifications. Spawn the watcher once: ${buildMonitorSpawn(logPath)}. Then ${buildReplyRecipe()}.`;
@@ -4,6 +4,7 @@ import {
4
4
  } from "./chunk-NR4Y54OL.js";
5
5
 
6
6
  // src/tandem/event-stream.ts
7
+ import { Agent } from "undici";
7
8
  var STALE_FLOOR_MS = 9e4;
8
9
  var STALE_INTERVAL_MULTIPLIER = 3;
9
10
  var STALE_CHECK_INTERVAL_MS = 5e3;
@@ -71,6 +72,10 @@ function createBackoffController(options = {}) {
71
72
  var LOG_HEARTBEAT_INTERVAL_MS = 6e4;
72
73
  async function startEventStream(opts) {
73
74
  let stopped = false;
75
+ const streamDispatcher = new Agent({
76
+ bodyTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS,
77
+ headersTimeout: UNDICI_DEFAULT_BODY_TIMEOUT_MS
78
+ });
74
79
  const cursors = /* @__PURE__ */ new Map();
75
80
  let currentController = new AbortController();
76
81
  let connectGeneration = 0;
@@ -95,6 +100,7 @@ async function startEventStream(opts) {
95
100
  currentController,
96
101
  isReconnect,
97
102
  state,
103
+ streamDispatcher,
98
104
  (tandemId, cursor) => {
99
105
  const prev = cursors.get(tandemId);
100
106
  if (prev === void 0 || cursor > prev) cursors.set(tandemId, cursor);
@@ -125,6 +131,8 @@ async function startEventStream(opts) {
125
131
  stopped = true;
126
132
  state.connected = false;
127
133
  currentController.abort();
134
+ void streamDispatcher.close().catch(() => {
135
+ });
128
136
  });
129
137
  handle.reconnect = () => {
130
138
  if (stopped) return;
@@ -144,12 +152,12 @@ async function startEventStream(opts) {
144
152
  });
145
153
  return handle;
146
154
  }
147
- async function connectAndConsume(opts, cursors, controller, isReconnect, state, onCursor, onOpen) {
155
+ async function connectAndConsume(opts, cursors, controller, isReconnect, state, dispatcher, onCursor, onOpen) {
148
156
  const mapSince = cursors.size > 0 ? serializeCursorMap(cursors) : null;
149
- let res = await openStream(opts, controller, mapSince);
157
+ let res = await openStream(opts, controller, mapSince, dispatcher);
150
158
  if (res.status === 400 && mapSince !== null) {
151
159
  console.error("[event-stream] per-room ?since rejected (400) \u2014 falling back to bare cursor");
152
- res = await openStream(opts, controller, String(Math.min(...cursors.values())));
160
+ res = await openStream(opts, controller, String(Math.min(...cursors.values())), dispatcher);
153
161
  }
154
162
  if (!res.ok) throw new Error(`SSE connect failed: ${res.status}`);
155
163
  if (!res.body) throw new Error("SSE response has no body");
@@ -163,7 +171,7 @@ async function connectAndConsume(opts, cursors, controller, isReconnect, state,
163
171
  await consumeSse(res.body, opts, controller, state, onCursor);
164
172
  return true;
165
173
  }
166
- async function openStream(opts, controller, sinceValue) {
174
+ async function openStream(opts, controller, sinceValue, dispatcher) {
167
175
  const params = new URLSearchParams();
168
176
  if (sinceValue !== null) params.set("since", sinceValue);
169
177
  const url = `${opts.brokerUrl}/api/v2/tandems/stream${params.toString() ? "?" + params.toString() : ""}`;
@@ -183,7 +191,11 @@ async function openStream(opts, controller, sinceValue) {
183
191
  "Mcp-Session-Id": getSessionId(),
184
192
  Accept: "text/event-stream"
185
193
  },
186
- signal: controller.signal
194
+ signal: controller.signal,
195
+ // Isolate this long-lived stream onto its own connection pool so it can
196
+ // never block tool-call POSTs on the shared global pool. The dispatcher is
197
+ // owned by startEventStream (per-stream lifecycle) and closed on teardown.
198
+ dispatcher
187
199
  });
188
200
  }
189
201
  async function consumeSse(body, opts, controller, state, onCursor) {
package/dist/cli.js CHANGED
@@ -4,12 +4,12 @@ import {
4
4
  } from "./chunk-OGHDTFAX.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-PSVVRGJC.js";
8
- import "./chunk-XXFVWP6H.js";
7
+ } from "./chunk-BYV7CWNY.js";
8
+ import "./chunk-N35X22G7.js";
9
9
  import {
10
10
  pairedConfigPath
11
11
  } from "./chunk-YH27B6SW.js";
12
- import "./chunk-U4MNICQI.js";
12
+ import "./chunk-KH5YEJIM.js";
13
13
  import "./chunk-JXMVZEQ7.js";
14
14
  import "./chunk-NR4Y54OL.js";
15
15
  import {
@@ -19,9 +19,9 @@ import {
19
19
  import "./chunk-BLEGIR35.js";
20
20
  import {
21
21
  VERSION
22
- } from "./chunk-XIDCYUTK.js";
23
- import "./chunk-X672ZN7V.js";
24
- import "./chunk-2OLXXOT3.js";
22
+ } from "./chunk-ALZX3SGI.js";
23
+ import "./chunk-Q7T3WAST.js";
24
+ import "./chunk-PPTKGWFF.js";
25
25
  import "./chunk-VHKPWUX7.js";
26
26
 
27
27
  // src/cli.ts
@@ -44,7 +44,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
44
44
  });
45
45
  program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption("--type <type>", "Hook type: stop, user-prompt-submit, or codex-stop").action(async (opts) => {
46
46
  if (opts.type === "stop") {
47
- const { runStopHook } = await import("./stop-hook-QZTFH5WV.js");
47
+ const { runStopHook } = await import("./stop-hook-TCPJFGFF.js");
48
48
  await runStopHook();
49
49
  process.exit(0);
50
50
  } else if (opts.type === "user-prompt-submit") {
@@ -52,7 +52,7 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
52
52
  await runUserPromptSubmitHook();
53
53
  process.exit(0);
54
54
  } else if (opts.type === "codex-stop") {
55
- const { runCodexStopHook } = await import("./codex-stop-hook-VY7DOMAG.js");
55
+ const { runCodexStopHook } = await import("./codex-stop-hook-RWQSZTST.js");
56
56
  await runCodexStopHook();
57
57
  process.exit(0);
58
58
  } else {
@@ -78,7 +78,7 @@ Restart Claude Code for hooks to take effect.`
78
78
  program.command("send <tandem_id>").description(
79
79
  "Send a Tandem message using this machine's paired credentials (~/.kojee). Prints one JSON envelope to stdout: {ok, message_id, cursor, text} on success, {ok:false, error:<typed code>, message} on failure (exit 1)."
80
80
  ).requiredOption("--body <text>", "Message body (required)").option("--reply-to <message_id>", "Message id this send replies to").option("--kind <kind>", "Message kind: message | status (default: backend default)").action(async (tandemId, opts) => {
81
- const { runSendCli } = await import("./send-cli-IPADRDD2.js");
81
+ const { runSendCli } = await import("./send-cli-IYJF32PE.js");
82
82
  const { exitCode, envelope } = await runSendCli({
83
83
  tandemId,
84
84
  body: opts.body,
@@ -89,7 +89,7 @@ program.command("send <tandem_id>").description(
89
89
  process.exit(exitCode);
90
90
  });
91
91
  program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").action(async (filePath) => {
92
- const { runTail } = await import("./tail-stream-CEJZGSIR.js");
92
+ const { runTail } = await import("./tail-stream-4IMXI44S.js");
93
93
  try {
94
94
  await runTail(filePath);
95
95
  } catch (err) {
@@ -98,75 +98,70 @@ program.command("tail <path>").description("Stream a file's contents and follow
98
98
  }
99
99
  });
100
100
  program.command("doctor").description("Diagnose the kojee wake path (proxy, hook-server, SSE stream, event log, Monitor) and print the exact wake recipe").action(async () => {
101
- const { runDoctor } = await import("./doctor-U3MTB4GY.js");
101
+ const { runDoctor } = await import("./doctor-WVX3WCYX.js");
102
102
  const code = await runDoctor();
103
103
  process.exit(code);
104
104
  });
105
- program.command("init").description(
106
- "Set up kojee for a runtime (claude-code | hermes | openclaw | codex). Interactive when stdin is a TTY; `--runtime <id>` for non-interactive/CI. Run after `kojee-mcp pair`."
107
- ).option("--runtime <id>", "Target runtime: claude-code | hermes | openclaw | codex").option("--config-path <path>", "Override the runtime's MCP-config path").option("--hooks-path <path>", "Override the runtime's hooks-file path").option("--token <token>", "Gateway token \u2014 token mode (writes --token/--url into the MCP config)").option("--url <url>", "Broker base URL (token mode / pair URL; default https://rosie-staging.kojee.net)").option("--pair-code <code>", "Pair code \u2014 runs the pair flow (writes ~/.kojee/config.json) before configuring").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
108
- "--webhook-signature-format <preset>",
109
- 'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
110
- ).option(
111
- "--webhook-signature-header <name>",
112
- "Signature header name (default X-Kojee-Signature; overrides the preset's header)"
113
- ).option(
114
- "--webhook-signature-prefix <prefix>",
115
- "Literal string prepended to the hex digest (default empty; overrides the preset's prefix)"
116
- ).option("--uninstall", "Remove the kojee config for the chosen (or recorded) runtime").action(async (opts) => {
117
- const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
118
- if (opts.pairCode && !opts.uninstall) {
119
- const { runPair: runPair2 } = await import("./pair-P4ILCMT7.js");
120
- const { pairedConfigPath: pairedConfigPath2 } = await import("./paired-config-JTFLHMZ2.js");
121
- const { defaultPairedKeystorePath: defaultPairedKeystorePath2 } = await import("./keystore-XLEV3FL5.js");
122
- const url = (opts.url ?? "https://rosie-staging.kojee.net").replace(/\/+$/, "");
123
- try {
124
- const r = await runPair2({
125
- code: opts.pairCode,
126
- url,
127
- keystorePath: defaultPairedKeystorePath2(),
128
- configPath: pairedConfigPath2()
129
- });
130
- console.error(r.message);
131
- } catch (err) {
132
- console.error("[kojee-mcp init] Pairing failed:", err.message);
133
- process.exit(1);
134
- }
135
- }
136
- const { loadPairedConfig } = await import("./paired-config-JTFLHMZ2.js");
137
- const hasCredential = loadPairedConfig() !== null || opts.token !== void 0;
138
- if (!hasCredential && !interactive && !opts.uninstall) {
139
- console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init` \u2014 or pass --token/--pair-code, or run `init` in a terminal for the guided wizard.");
140
- process.exit(1);
141
- }
142
- const { runWizard } = await import("./wizard-Y3ULSO2C.js");
143
- const result = await runWizard({
144
- ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
145
- ...opts.uninstall ? { uninstall: true } : {},
146
- ...opts.configPath ? { configPath: opts.configPath } : {},
147
- ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
148
- // Token-mode threading (token + url ⇒ --token/--url in the written config).
149
- // Normalize the URL exactly like the proxy action does (strip trailing /).
150
- ...opts.token ? { token: opts.token } : {},
151
- ...opts.url ? { url: opts.url.replace(/\/+$/, "") } : {},
152
- ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
153
- ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
154
- ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
155
- ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
156
- ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
157
- interactive,
158
- ...interactive ? {
159
- promptRuntime: promptRuntimeFromTty,
160
- promptUrl: promptUrlFromTty,
161
- promptAuth: promptAuthFromTty,
162
- promptToken: promptTokenFromTty,
163
- promptPairCode: promptPairCodeFromTty,
164
- promptWebhookUrl: promptWebhookUrlFromTty
165
- } : {}
166
- });
167
- console.error(result.output);
168
- process.exit(result.exitCode);
169
- });
105
+ function addInstallOptions(cmd, runtimeHelp) {
106
+ return cmd.option("--runtime <id>", runtimeHelp).option("--config-path <path>", "Override the runtime's MCP-config path").option("--hooks-path <path>", "Override the runtime's hooks-file path").option("--token <token>", "Gateway token \u2014 token mode (writes --token/--url into the MCP config; requires --url)").option("--url <url>", "Broker base URL (token mode / pair URL; default https://rosie-staging.kojee.net)").option("--pair <code>", "Pair code \u2014 runs the pair flow before configuring (alias of --pair-code)").option("--pair-code <code>", "Pair code \u2014 runs the pair flow (writes ~/.kojee/config.json) before configuring").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
107
+ "--webhook-signature-format <preset>",
108
+ 'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
109
+ ).option(
110
+ "--webhook-signature-header <name>",
111
+ "Signature header name (default X-Kojee-Signature; overrides the preset's header)"
112
+ ).option(
113
+ "--webhook-signature-prefix <prefix>",
114
+ "Literal string prepended to the hex digest (default empty; overrides the preset's prefix)"
115
+ ).option("--uninstall", "Remove the kojee config for the chosen (or recorded) runtime");
116
+ }
117
+ function makeInstallAction(verb) {
118
+ return async (opts) => {
119
+ const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
120
+ const { runSetup, resolvePairCode } = await import("./setup-handler-7HOFAD7C.js");
121
+ const pairCode = resolvePairCode(opts);
122
+ const result = await runSetup({
123
+ verb,
124
+ ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
125
+ ...opts.configPath ? { configPath: opts.configPath } : {},
126
+ ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
127
+ ...opts.token ? { token: opts.token } : {},
128
+ ...opts.url ? { url: opts.url } : {},
129
+ ...pairCode ? { pairCode } : {},
130
+ ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
131
+ ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
132
+ ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
133
+ ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
134
+ ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
135
+ ...opts.uninstall ? { uninstall: true } : {},
136
+ interactive,
137
+ ...interactive ? {
138
+ prompts: {
139
+ promptRuntime: promptRuntimeFromTty,
140
+ promptUrl: promptUrlFromTty,
141
+ promptAuth: promptAuthFromTty,
142
+ promptToken: promptTokenFromTty,
143
+ promptPairCode: promptPairCodeFromTty,
144
+ promptWebhookUrl: promptWebhookUrlFromTty
145
+ }
146
+ } : {}
147
+ });
148
+ for (const m of result.messages) console.error(m);
149
+ console.error(result.output);
150
+ process.exit(result.exitCode);
151
+ };
152
+ }
153
+ addInstallOptions(
154
+ program.command("init").description(
155
+ "Set up kojee for a runtime (claude-code | hermes | openclaw | codex). Interactive when stdin is a TTY; `--runtime <id>` for non-interactive/CI. Run after `kojee-mcp pair`."
156
+ ),
157
+ "Target runtime: claude-code | hermes | openclaw | codex"
158
+ ).action(makeInstallAction("init"));
159
+ addInstallOptions(
160
+ program.command("setup").description(
161
+ "One-line install: pair + write the runtime's MCP config + hooks in one shot (claude-code | codex). `--pair <code> --url <broker> --runtime <id>`, or the token form `--token <T> --url <U> --runtime <id>` for CI."
162
+ ),
163
+ "Target runtime: claude-code | codex"
164
+ ).action(makeInstallAction("setup"));
170
165
  async function withReadline(fn) {
171
166
  const readline = await import("readline/promises");
172
167
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
@@ -4,9 +4,9 @@ import {
4
4
  decideCodexStopHook,
5
5
  defaultPeekPending,
6
6
  runCodexStopHook
7
- } from "./chunk-35XBRG3V.js";
7
+ } from "./chunk-KFDZWNQO.js";
8
8
  import "./chunk-LSUB6QMP.js";
9
- import "./chunk-X672ZN7V.js";
9
+ import "./chunk-Q7T3WAST.js";
10
10
  export {
11
11
  CODEX_PEEK_BUDGET_MS,
12
12
  codexPendingMarkerPath,
@@ -18,7 +18,7 @@ import "./chunk-BLEGIR35.js";
18
18
  import {
19
19
  buildMonitorSpawn,
20
20
  buildReplyRecipe
21
- } from "./chunk-X672ZN7V.js";
21
+ } from "./chunk-Q7T3WAST.js";
22
22
  import {
23
23
  deriveDiscoveryKey,
24
24
  findClaudeAncestorPid
@@ -328,7 +328,7 @@ function formatDoctorReport(report) {
328
328
  async function runDoctor() {
329
329
  const { readRecordedRuntime } = await import("./runtime-record-WO4IECM6.js");
330
330
  if (readRecordedRuntime() === "codex") {
331
- const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-PA3WO6LR.js");
331
+ const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-Z5M4SFBG.js");
332
332
  const report2 = collectCodexDoctorReport();
333
333
  console.error(formatCodexDoctorReport(report2));
334
334
  return report2.verdict === "broken" ? 1 : 0;
@@ -9,7 +9,7 @@ import {
9
9
  import "./chunk-BLEGIR35.js";
10
10
  import {
11
11
  CODEX_LISTEN_CAP_MS
12
- } from "./chunk-X672ZN7V.js";
12
+ } from "./chunk-Q7T3WAST.js";
13
13
 
14
14
  // src/doctor-codex.ts
15
15
  import fs from "fs";
@@ -6,7 +6,7 @@ import {
6
6
  sanitizeDisplayname,
7
7
  serializeCursorMap,
8
8
  startEventStream
9
- } from "./chunk-74XFVX6Z.js";
9
+ } from "./chunk-XN3IC4B5.js";
10
10
  import "./chunk-NR4Y54OL.js";
11
11
  export {
12
12
  UNDICI_DEFAULT_BODY_TIMEOUT_MS,
@@ -3,8 +3,8 @@ import {
3
3
  httpStatusForEnvelope,
4
4
  parseSendRequest,
5
5
  sendFailure
6
- } from "./chunk-DXJ6QLSJ.js";
7
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-PKOBR7ZN.js";
7
+ import "./chunk-PPTKGWFF.js";
8
8
 
9
9
  // src/tandem/hook-server.ts
10
10
  import crypto from "crypto";
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-PSVVRGJC.js";
5
- import "./chunk-XXFVWP6H.js";
6
- import "./chunk-U4MNICQI.js";
4
+ } from "./chunk-BYV7CWNY.js";
5
+ import "./chunk-N35X22G7.js";
6
+ import "./chunk-KH5YEJIM.js";
7
7
  import "./chunk-JXMVZEQ7.js";
8
8
  import "./chunk-NR4Y54OL.js";
9
9
  import "./chunk-CH32ELFX.js";
10
10
  import "./chunk-BLEGIR35.js";
11
- import "./chunk-XIDCYUTK.js";
12
- import "./chunk-X672ZN7V.js";
13
- import "./chunk-2OLXXOT3.js";
11
+ import "./chunk-ALZX3SGI.js";
12
+ import "./chunk-Q7T3WAST.js";
13
+ import "./chunk-PPTKGWFF.js";
14
14
  import "./chunk-VHKPWUX7.js";
15
15
  export {
16
16
  listTandemIds,
package/dist/lib.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  normalizeBackendEvent,
6
6
  sanitizeDisplayname,
7
7
  startEventStream
8
- } from "./chunk-74XFVX6Z.js";
8
+ } from "./chunk-XN3IC4B5.js";
9
9
  import {
10
10
  loadPairedConfig,
11
11
  pairedConfigPath
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  GatewayClient,
15
15
  applyStableSessionId
16
- } from "./chunk-U4MNICQI.js";
16
+ } from "./chunk-KH5YEJIM.js";
17
17
  import {
18
18
  AuthModule
19
19
  } from "./chunk-JXMVZEQ7.js";
@@ -29,7 +29,7 @@ import {
29
29
  saveKeystore
30
30
  } from "./chunk-CH32ELFX.js";
31
31
  import "./chunk-BLEGIR35.js";
32
- import "./chunk-2OLXXOT3.js";
32
+ import "./chunk-PPTKGWFF.js";
33
33
  export {
34
34
  AuthModule,
35
35
  GatewayClient,
@@ -1,12 +1,12 @@
1
- import "./chunk-35XBRG3V.js";
1
+ import "./chunk-KFDZWNQO.js";
2
2
  import "./chunk-PHXO5P25.js";
3
3
  import "./chunk-LSUB6QMP.js";
4
4
  import {
5
5
  claudeCodeAdapter
6
- } from "./chunk-XXFVWP6H.js";
7
- import "./chunk-XIDCYUTK.js";
8
- import "./chunk-X672ZN7V.js";
9
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-N35X22G7.js";
7
+ import "./chunk-ALZX3SGI.js";
8
+ import "./chunk-Q7T3WAST.js";
9
+ import "./chunk-PPTKGWFF.js";
10
10
 
11
11
  // src/delivery/lib/fanout.ts
12
12
  async function deliverEvent(event, sinks) {
@@ -91,7 +91,7 @@ function createClaudeCodeDelivery() {
91
91
  name: "claude-code",
92
92
  async start(ctx) {
93
93
  const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
94
- const { startHookServer } = await import("./hook-server-XK2NHLJV.js");
94
+ const { startHookServer } = await import("./hook-server-CWBH6PFL.js");
95
95
  const {
96
96
  writeDiscoveryByKey,
97
97
  cleanupDiscoveryByKey,
@@ -101,8 +101,8 @@ function createClaudeCodeDelivery() {
101
101
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
102
102
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
103
103
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
104
- const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
105
- const { createMcpServer } = await import("./server-LTFG4GMV.js");
104
+ const { startEventStream } = await import("./event-stream-SYBUJ5EH.js");
105
+ const { createMcpServer } = await import("./server-R24AVNTI.js");
106
106
  const { deriveDiscoveryKey } = await import("./ancestry-ONFBQEP5.js");
107
107
  sweepStaleDiscovery();
108
108
  sweepStaleEventLogs();
@@ -278,8 +278,8 @@ function createWebhookDelivery(name) {
278
278
  const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
279
279
  const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
280
280
  const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
281
- const { startEventStream } = await import("./event-stream-XX5EZ6HN.js");
282
- const { createMcpServer } = await import("./server-LTFG4GMV.js");
281
+ const { startEventStream } = await import("./event-stream-SYBUJ5EH.js");
282
+ const { createMcpServer } = await import("./server-R24AVNTI.js");
283
283
  sweepStaleEventLogs();
284
284
  eventLog = startEventLog({
285
285
  key: ctx.instanceKey,
@@ -3,20 +3,20 @@ import {
3
3
  executeSend,
4
4
  parseSendRequest,
5
5
  sendFailure
6
- } from "./chunk-DXJ6QLSJ.js";
6
+ } from "./chunk-PKOBR7ZN.js";
7
7
  import {
8
8
  loadPairedConfig
9
9
  } from "./chunk-YH27B6SW.js";
10
10
  import {
11
11
  GatewayClient,
12
12
  applyStableSessionId
13
- } from "./chunk-U4MNICQI.js";
13
+ } from "./chunk-KH5YEJIM.js";
14
14
  import "./chunk-NR4Y54OL.js";
15
15
  import {
16
16
  loadKeystore
17
17
  } from "./chunk-CH32ELFX.js";
18
18
  import "./chunk-BLEGIR35.js";
19
- import "./chunk-2OLXXOT3.js";
19
+ import "./chunk-PPTKGWFF.js";
20
20
 
21
21
  // src/tandem/send-cli.ts
22
22
  import os from "os";
@@ -3,9 +3,9 @@ import {
3
3
  createMcpServer,
4
4
  executeToolCall,
5
5
  startMcpServer
6
- } from "./chunk-XIDCYUTK.js";
7
- import "./chunk-X672ZN7V.js";
8
- import "./chunk-2OLXXOT3.js";
6
+ } from "./chunk-ALZX3SGI.js";
7
+ import "./chunk-Q7T3WAST.js";
8
+ import "./chunk-PPTKGWFF.js";
9
9
  export {
10
10
  buildChannelInstructions,
11
11
  createMcpServer,
@@ -25,13 +25,16 @@ import {
25
25
  resolveSignatureEmission,
26
26
  resolveWebhookConfig
27
27
  } from "./chunk-V5VZPYMZ.js";
28
+ import {
29
+ loadPairedConfig
30
+ } from "./chunk-YH27B6SW.js";
28
31
  import {
29
32
  secureFile
30
33
  } from "./chunk-BLEGIR35.js";
31
34
  import {
32
35
  CODEX_LISTEN_CAP_MS,
33
36
  buildWebhookReceiverNote
34
- } from "./chunk-X672ZN7V.js";
37
+ } from "./chunk-Q7T3WAST.js";
35
38
 
36
39
  // src/wizard/wizard.ts
37
40
  import crypto2 from "crypto";
@@ -824,11 +827,116 @@ registerBuiltinInstaller(
824
827
  ["pair-credential", "in-process-plugin"],
825
828
  (o) => configureOpenclaw(o)
826
829
  );
830
+
831
+ // src/wizard/setup-handler.ts
832
+ var SETUP_SUPPORTED_RUNTIMES = ["claude-code", "codex"];
833
+ function isSetupRuntime(value) {
834
+ return SETUP_SUPPORTED_RUNTIMES.includes(value);
835
+ }
836
+ function resolvePairCode(opts) {
837
+ return opts.pair ?? opts.pairCode;
838
+ }
839
+ async function defaultRunPair(args) {
840
+ const { runPair } = await import("./pair-P4ILCMT7.js");
841
+ const { pairedConfigPath } = await import("./paired-config-JTFLHMZ2.js");
842
+ const { defaultPairedKeystorePath } = await import("./keystore-XLEV3FL5.js");
843
+ return runPair({
844
+ code: args.code,
845
+ url: args.url,
846
+ keystorePath: defaultPairedKeystorePath(),
847
+ configPath: pairedConfigPath()
848
+ });
849
+ }
850
+ async function runSetup(opts) {
851
+ const messages = [];
852
+ const requested = opts.runtime ?? "claude-code";
853
+ if (opts.verb === "setup" && opts.runtime !== void 0 && !isSetupRuntime(opts.runtime)) {
854
+ const supported = SETUP_SUPPORTED_RUNTIMES.join(", ");
855
+ const output = isWizardRuntime(opts.runtime) ? `\`setup\` does not support --runtime ${opts.runtime} yet (coming soon). Supported by \`setup\`: ${supported}. For ${opts.runtime}, use \`kojee-mcp init --runtime ${opts.runtime}\` for now.` : `Unknown --runtime "${opts.runtime}". \`setup\` supports: ${supported}. (For hermes/openclaw use \`kojee-mcp init\`.)`;
856
+ return { runtime: requested, messages, output, exitCode: 2 };
857
+ }
858
+ if (opts.token !== void 0 && (opts.url === void 0 || opts.url.trim() === "") && !opts.uninstall) {
859
+ return {
860
+ runtime: requested,
861
+ messages,
862
+ output: "--token requires --url (the broker URL the Kojee UI gave you). Re-run with --url <broker-url>. Nothing was written.",
863
+ exitCode: 2
864
+ };
865
+ }
866
+ const url = opts.url ? opts.url.replace(/\/+$/, "") : void 0;
867
+ if (opts.pairCode && !opts.uninstall) {
868
+ const pair = opts.runPair ?? defaultRunPair;
869
+ const pairUrl = url ?? DEFAULT_BROKER_URL;
870
+ try {
871
+ const r = await pair({ code: opts.pairCode, url: pairUrl });
872
+ messages.push(r.message);
873
+ } catch (err) {
874
+ return {
875
+ runtime: requested,
876
+ messages,
877
+ output: `Pairing failed: ${err.message}`,
878
+ exitCode: 1
879
+ };
880
+ }
881
+ }
882
+ const loadCfg = opts.loadPairedConfig ?? loadPairedConfig;
883
+ const hasCredential = loadCfg() !== null || opts.token !== void 0;
884
+ if (!hasCredential && !opts.interactive && !opts.uninstall) {
885
+ return {
886
+ runtime: requested,
887
+ messages,
888
+ output: "Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run \u2014 or pass --pair <code> (or --token <T> --url <U>), or run in a terminal for the guided wizard.",
889
+ exitCode: 1
890
+ };
891
+ }
892
+ const result = await runWizard({
893
+ ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
894
+ ...opts.uninstall ? { uninstall: true } : {},
895
+ ...opts.configPath ? { configPath: opts.configPath } : {},
896
+ ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
897
+ // Token mode (token + url) threads --token/--url into the written config.
898
+ ...opts.token ? { token: opts.token } : {},
899
+ ...url ? { url } : {},
900
+ ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
901
+ ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
902
+ ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
903
+ ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
904
+ ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
905
+ interactive: opts.interactive,
906
+ ...opts.interactive && opts.prompts ? opts.prompts : {}
907
+ });
908
+ if (opts.verb === "setup" && result.exitCode === 0 && !opts.uninstall) {
909
+ return {
910
+ runtime: result.runtime,
911
+ messages,
912
+ output: crispSetupSummary(result.runtime, opts, url),
913
+ exitCode: 0
914
+ };
915
+ }
916
+ return { runtime: result.runtime, messages, output: result.output, exitCode: result.exitCode };
917
+ }
918
+ function crispSetupSummary(runtime, opts, url) {
919
+ const authLine = opts.token ? ` Auth: token mode (--token/--url${url ? `, ${url}` : ""})` : " Auth: paired credentials (~/.kojee/config.json)";
920
+ const lines = [];
921
+ if (runtime === "codex") {
922
+ lines.push("\u2713 kojee is set up for Codex.");
923
+ lines.push(` MCP config: ${opts.configPath ?? "~/.codex/config.toml"} ([mcp_servers.kojee])`);
924
+ lines.push(` Stop hook: ${opts.hooksPath ?? "~/.codex/hooks.json"} (codex-stop)`);
925
+ lines.push(authLine);
926
+ lines.push("");
927
+ lines.push("Next: restart Codex to connect.");
928
+ } else {
929
+ lines.push("\u2713 kojee is set up for Claude Code.");
930
+ lines.push(` MCP config: ${opts.configPath ?? "~/.claude.json"} (mcpServers.kojee)`);
931
+ lines.push(` Hooks: ${opts.hooksPath ?? "~/.claude/settings.json"} (Stop + UserPromptSubmit)`);
932
+ lines.push(authLine);
933
+ lines.push("");
934
+ lines.push("Next: restart Claude Code to connect.");
935
+ }
936
+ return lines.join("\n");
937
+ }
827
938
  export {
828
- CODEX_UNVERIFIED_NOTE,
829
- DEFAULT_BROKER_URL,
830
- generateWebhookSecret,
831
- resolveRuntime,
832
- resolveWizardWebhook,
833
- runWizard
939
+ SETUP_SUPPORTED_RUNTIMES,
940
+ resolvePairCode,
941
+ runSetup
834
942
  };
@@ -18,7 +18,7 @@ import {
18
18
  import "./chunk-BLEGIR35.js";
19
19
  import {
20
20
  buildMonitorNudge
21
- } from "./chunk-X672ZN7V.js";
21
+ } from "./chunk-Q7T3WAST.js";
22
22
  import {
23
23
  deriveDiscoveryKey,
24
24
  findClaudeAncestorPid
@@ -7,7 +7,7 @@ import {
7
7
  import "./chunk-DO42NPNR.js";
8
8
  import {
9
9
  createAdaptiveWatchdog
10
- } from "./chunk-74XFVX6Z.js";
10
+ } from "./chunk-XN3IC4B5.js";
11
11
  import "./chunk-NR4Y54OL.js";
12
12
  import "./chunk-BLEGIR35.js";
13
13
 
@@ -15,6 +15,9 @@ import "./chunk-BLEGIR35.js";
15
15
  import fs from "fs";
16
16
  var STATUS_LINE_RE = new RegExp(`^\\[[^\\]]*\\] ${STATUS_LINE_PREFIX} (.*)$`);
17
17
  var HEARTBEAT_FIELDS_RE = /(^|\s)status=heartbeat(\s|$)/;
18
+ var ROTATED_FIELDS_RE = /(^|\s)status=rotated(\s|$)/;
19
+ var ROTATED_DROPPED_RE = /(^|\s)dropped=(\d+)(\s|$)/;
20
+ var MESSAGE_CURSOR_RE = /(^|\s)cursor=(\S+)(\s|$)/;
18
21
  var HEARTBEAT_STALE_FLOOR_MS = envInt("KOJEE_TAIL_STALE_MS", 12e4);
19
22
  var STALE_CHECK_INTERVAL_MS = envInt("KOJEE_TAIL_CHECK_MS", 5e3);
20
23
  function envInt(name, fallback) {
@@ -38,14 +41,18 @@ async function runTail(messagesPath) {
38
41
  }
39
42
  }
40
43
  };
44
+ let lastCursor = null;
41
45
  function onMessageLine(line) {
42
46
  lastLifeAt = Date.now();
43
47
  if (staleAnnounced) {
44
48
  staleAnnounced = false;
45
49
  process.stdout.write("[kojee] stream recovered \u2014 events flowing again.\n");
46
50
  }
51
+ const cm = MESSAGE_CURSOR_RE.exec(line);
52
+ if (cm && cm[2]) lastCursor = cm[2];
47
53
  process.stdout.write(line + "\n");
48
54
  }
55
+ let statusSeeded = false;
49
56
  function onStatusLine(line) {
50
57
  const now = Date.now();
51
58
  const m = STATUS_LINE_RE.exec(line);
@@ -58,12 +65,26 @@ async function runTail(messagesPath) {
58
65
  staleAnnounced = false;
59
66
  process.stdout.write("[kojee] stream recovered \u2014 heartbeats resumed.\n");
60
67
  }
68
+ if (statusSeeded && ROTATED_FIELDS_RE.test(fields)) {
69
+ const dm = ROTATED_DROPPED_RE.exec(fields);
70
+ const dropped = dm ? dm[2] : "?";
71
+ const since = lastCursor ? `since=${lastCursor}` : "since=<your last cursor>";
72
+ process.stdout.write(
73
+ `[kojee] WARNING: event log rotated (dropped=${dropped}) \u2014 older events were truncated and may have been missed; run tandem_messages(${since}) to backfill.
74
+ `
75
+ );
76
+ }
61
77
  }
62
78
  const followers = [
63
79
  makeFollower(messagesPath, onMessageLine, touchHeartbeat),
64
80
  makeFollower(statusPath, onStatusLine, touchHeartbeat)
65
81
  ];
66
- void Promise.all(followers.map((f) => f.start()));
82
+ void Promise.all([
83
+ followers[0].start(),
84
+ followers[1].start().then(() => {
85
+ statusSeeded = true;
86
+ })
87
+ ]);
67
88
  const staleTimer = setInterval(() => {
68
89
  if (staleAnnounced) return;
69
90
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.19",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -33,6 +33,7 @@
33
33
  "jose": "^5.0.0",
34
34
  "ps-list": "^8.1.1",
35
35
  "ulidx": "^2.3.0",
36
+ "undici": "^8.5.0",
36
37
  "zod": "^3.22.0"
37
38
  },
38
39
  "devDependencies": {