kojee-mcp 0.5.0 → 0.5.3
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 +110 -4
- package/dist/{chunk-VLZADEFC.js → chunk-2TUAFAIW.js} +6 -9
- package/dist/chunk-BLEGIR35.js +43 -0
- package/dist/chunk-C6GZ2L2W.js +38 -0
- package/dist/{chunk-W6YRLSD4.js → chunk-DO42NPNR.js} +9 -16
- package/dist/chunk-EW72ZNQL.js +39 -0
- package/dist/chunk-F7L25L2J.js +60 -0
- package/dist/chunk-LVL25VLO.js +22 -0
- package/dist/chunk-SQL56SEB.js +14 -0
- package/dist/{chunk-QB22PD6T.js → chunk-WBMX4CHB.js} +29 -9
- package/dist/{chunk-LCFCCWMM.js → chunk-YEC7IHIG.js} +136 -78
- package/dist/{chunk-GBOTBYEP.js → chunk-YH27B6SW.js} +7 -8
- package/dist/chunk-ZW4SW7LJ.js +225 -0
- package/dist/cli.js +54 -80
- package/dist/codex-stop-hook-JOTBCS5K.js +72 -0
- package/dist/{doctor-GILTOH2R.js → doctor-TSHOMT5X.js} +29 -14
- package/dist/doctor-codex-BMI5JOO6.js +130 -0
- package/dist/{event-log-R6VW6GAF.js → event-log-RSTM4PLL.js} +3 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -3
- package/dist/{install-D2HIPOMT.js → install-WBIUVBZW.js} +5 -4
- package/dist/{paired-config-RB4SABOS.js → paired-config-JTFLHMZ2.js} +2 -1
- package/dist/runtime-record-WO4IECM6.js +14 -0
- package/dist/runtimes-CO43XUUK.js +12 -0
- package/dist/{session-discovery-QE5TTAPS.js → session-discovery-FNMJGFPM.js} +2 -1
- package/dist/{stop-hook-VLQS6QPR.js → stop-hook-SEPWWETV.js} +6 -5
- package/dist/{tail-stream-UZ42UIWO.js → tail-stream-BYKO4DW6.js} +4 -3
- package/dist/{user-prompt-submit-hook-C42DPDBO.js → user-prompt-submit-hook-ARPEO6FF.js} +2 -1
- package/dist/webhook-config-5TLLX7RA.js +10 -0
- package/dist/webhook-sink-7OYZBWXA.js +163 -0
- package/dist/wizard-7KHD5JT4.js +265 -0
- package/package.json +1 -1
- package/dist/chunk-E26AHU6J.js +0 -27
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadPairedConfig,
|
|
4
|
+
pairedConfigPath,
|
|
5
|
+
savePairedConfig
|
|
6
|
+
} from "./chunk-YH27B6SW.js";
|
|
2
7
|
import {
|
|
3
8
|
AuthModule,
|
|
4
9
|
VERSION,
|
|
5
10
|
startProxy
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-QB22PD6T.js";
|
|
8
|
-
import "./chunk-E26AHU6J.js";
|
|
11
|
+
} from "./chunk-YEC7IHIG.js";
|
|
9
12
|
import "./chunk-BJMASMKX.js";
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
savePairedConfig
|
|
14
|
-
} from "./chunk-GBOTBYEP.js";
|
|
13
|
+
import "./chunk-C6GZ2L2W.js";
|
|
14
|
+
import "./chunk-WBMX4CHB.js";
|
|
15
|
+
import "./chunk-BLEGIR35.js";
|
|
15
16
|
|
|
16
17
|
// src/cli.ts
|
|
17
18
|
import { Command } from "commander";
|
|
@@ -82,22 +83,26 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
82
83
|
process.exit(1);
|
|
83
84
|
}
|
|
84
85
|
});
|
|
85
|
-
program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption("--type <type>", "Hook type: stop
|
|
86
|
+
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) => {
|
|
86
87
|
if (opts.type === "stop") {
|
|
87
|
-
const { runStopHook } = await import("./stop-hook-
|
|
88
|
+
const { runStopHook } = await import("./stop-hook-SEPWWETV.js");
|
|
88
89
|
await runStopHook();
|
|
89
90
|
process.exit(0);
|
|
90
91
|
} else if (opts.type === "user-prompt-submit") {
|
|
91
|
-
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-
|
|
92
|
+
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-ARPEO6FF.js");
|
|
92
93
|
await runUserPromptSubmitHook();
|
|
93
94
|
process.exit(0);
|
|
95
|
+
} else if (opts.type === "codex-stop") {
|
|
96
|
+
const { runCodexStopHook } = await import("./codex-stop-hook-JOTBCS5K.js");
|
|
97
|
+
await runCodexStopHook();
|
|
98
|
+
process.exit(0);
|
|
94
99
|
} else {
|
|
95
|
-
console.error(`Unknown hook type: ${opts.type}. Expected 'stop'
|
|
100
|
+
console.error(`Unknown hook type: ${opts.type}. Expected 'stop', 'user-prompt-submit', or 'codex-stop'.`);
|
|
96
101
|
process.exit(1);
|
|
97
102
|
}
|
|
98
103
|
});
|
|
99
104
|
program.command("install-hooks").description("Install kojee Stop + UserPromptSubmit hooks in ~/.claude/settings.json (idempotent)").option("--hooks-path <path>", "Override default ~/.claude/settings.json").option("--uninstall", "Remove kojee hook entries instead of installing them").action(async (opts) => {
|
|
100
|
-
const { installHooks, uninstallHooks } = await import("./install-
|
|
105
|
+
const { installHooks, uninstallHooks } = await import("./install-WBIUVBZW.js");
|
|
101
106
|
if (opts.uninstall) {
|
|
102
107
|
const removed = uninstallHooks({ hooksPath: opts.hooksPath });
|
|
103
108
|
console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
|
|
@@ -112,7 +117,7 @@ Restart Claude Code for hooks to take effect.`
|
|
|
112
117
|
}
|
|
113
118
|
});
|
|
114
119
|
program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").action(async (filePath) => {
|
|
115
|
-
const { runTail } = await import("./tail-stream-
|
|
120
|
+
const { runTail } = await import("./tail-stream-BYKO4DW6.js");
|
|
116
121
|
try {
|
|
117
122
|
await runTail(filePath);
|
|
118
123
|
} catch (err) {
|
|
@@ -121,79 +126,47 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
121
126
|
}
|
|
122
127
|
});
|
|
123
128
|
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 () => {
|
|
124
|
-
const { runDoctor } = await import("./doctor-
|
|
129
|
+
const { runDoctor } = await import("./doctor-TSHOMT5X.js");
|
|
125
130
|
const code = await runDoctor();
|
|
126
131
|
process.exit(code);
|
|
127
132
|
});
|
|
128
|
-
program.command("init").description(
|
|
129
|
-
|
|
133
|
+
program.command("init").description(
|
|
134
|
+
"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`."
|
|
135
|
+
).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("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option("--uninstall", "Remove the kojee config for the chosen (or recorded) runtime").action(async (opts) => {
|
|
136
|
+
const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-JTFLHMZ2.js");
|
|
130
137
|
if (loadPairedConfig2() === null && !opts.uninstall) {
|
|
131
138
|
console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init`.");
|
|
132
139
|
process.exit(1);
|
|
133
140
|
}
|
|
134
|
-
const {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
const { runWizard } = await import("./wizard-7KHD5JT4.js");
|
|
142
|
+
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
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
|
+
...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
|
|
149
|
+
...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
|
|
150
|
+
interactive,
|
|
151
|
+
...interactive ? { promptRuntime: promptRuntimeFromTty } : {}
|
|
152
|
+
});
|
|
153
|
+
console.error(result.output);
|
|
154
|
+
process.exit(result.exitCode);
|
|
142
155
|
});
|
|
143
|
-
function
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
lines.push(` mcpServers.kojee ${tick(t.mcpServer)}`);
|
|
157
|
-
if (t.kind === "cli") {
|
|
158
|
-
if (t.hooksPath) {
|
|
159
|
-
lines.push(` ${t.hooksPath} (hooks)`);
|
|
160
|
-
}
|
|
161
|
-
lines.push(` hooks.Stop ${tick(t.stopHook)}`);
|
|
162
|
-
lines.push(` hooks.UserPromptSubmit ${tick(t.userPromptSubmitHook)}`);
|
|
163
|
-
} else {
|
|
164
|
-
lines.push(` (hooks not applicable for Claude.app agent mode)`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
lines.push("");
|
|
168
|
-
const desktopWritten = report.targets.some((t) => t.kind === "desktop" && t.mcpServer === "added");
|
|
169
|
-
if (desktopWritten) {
|
|
170
|
-
lines.push(
|
|
171
|
-
"Existing Claude.app agent-mode sessions snapshotted the previous config",
|
|
172
|
-
"and won't pick up this change automatically. Start a NEW agent-mode",
|
|
173
|
-
"session (not a resumed one) to use the updated kojee config.",
|
|
174
|
-
""
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
lines.push("To verify: in any new CC session, run /mcp and confirm `kojee` is listed.");
|
|
178
|
-
lines.push(" run /hooks and confirm both Stop and UserPromptSubmit show the kojee entries.");
|
|
179
|
-
lines.push("To remove: `kojee-mcp init --uninstall`");
|
|
180
|
-
return lines.join("\n");
|
|
181
|
-
}
|
|
182
|
-
function formatUninstall(report) {
|
|
183
|
-
const lines = ["Removing kojee from Claude:"];
|
|
184
|
-
for (const t of report.targets) {
|
|
185
|
-
const label = t.kind === "cli" ? "CLI" : "Claude.app";
|
|
186
|
-
lines.push("");
|
|
187
|
-
lines.push(` ${t.path} (${label})`);
|
|
188
|
-
lines.push(` mcpServers.kojee ${t.mcpServer ? "\u2713 removed" : "\u2014 not found"}`);
|
|
189
|
-
if (t.kind === "cli") {
|
|
190
|
-
if (t.hooksPath) {
|
|
191
|
-
lines.push(` ${t.hooksPath} (hooks)`);
|
|
192
|
-
}
|
|
193
|
-
lines.push(` hook entries ${t.hooks ? "\u2713 removed" : "\u2014 not found"}`);
|
|
194
|
-
}
|
|
156
|
+
async function promptRuntimeFromTty() {
|
|
157
|
+
const { RUNTIME_MENU } = await import("./runtimes-CO43XUUK.js");
|
|
158
|
+
const readline = await import("readline/promises");
|
|
159
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
160
|
+
try {
|
|
161
|
+
const menu = RUNTIME_MENU.map((m) => `[${m.index}] ${m.runtime}`).join(" ");
|
|
162
|
+
const answer = (await rl.question(`Which runtime is this proxy for? ${menu}
|
|
163
|
+
> `)).trim();
|
|
164
|
+
const byIndex = RUNTIME_MENU.find((m) => String(m.index) === answer);
|
|
165
|
+
if (byIndex) return byIndex.runtime;
|
|
166
|
+
return answer;
|
|
167
|
+
} finally {
|
|
168
|
+
rl.close();
|
|
195
169
|
}
|
|
196
|
-
return lines.join("\n");
|
|
197
170
|
}
|
|
198
171
|
program.option("--token <token>", "Gateway token (for token-mode)").option("--url <url>", "Broker base URL (for token-mode; required if --token is passed)").option(
|
|
199
172
|
"--keystore-path <path>",
|
|
@@ -202,6 +175,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
202
175
|
let token = opts.token;
|
|
203
176
|
let url = opts.url;
|
|
204
177
|
let keystorePath = opts.keystorePath;
|
|
178
|
+
const authMode = token ? "token" : "paired";
|
|
205
179
|
if (token) {
|
|
206
180
|
if (!url) {
|
|
207
181
|
console.error("--url is required when --token is provided");
|
|
@@ -210,7 +184,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
210
184
|
url = url.replace(/\/+$/, "");
|
|
211
185
|
keystorePath ??= deriveKeystorePath(token);
|
|
212
186
|
} else {
|
|
213
|
-
const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-
|
|
187
|
+
const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-JTFLHMZ2.js");
|
|
214
188
|
const cfg = loadPairedConfig2();
|
|
215
189
|
if (!cfg) {
|
|
216
190
|
console.error(
|
|
@@ -223,7 +197,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
223
197
|
keystorePath ??= defaultPairedKeystorePath();
|
|
224
198
|
}
|
|
225
199
|
try {
|
|
226
|
-
await startProxy({ token, url, keystorePath });
|
|
200
|
+
await startProxy({ token, url, keystorePath, authMode });
|
|
227
201
|
} catch (err) {
|
|
228
202
|
console.error("[kojee-mcp] Fatal error:", err);
|
|
229
203
|
process.exit(1);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readHookStdin
|
|
3
|
+
} from "./chunk-LSUB6QMP.js";
|
|
4
|
+
import {
|
|
5
|
+
buildCodexWakeReason
|
|
6
|
+
} from "./chunk-C6GZ2L2W.js";
|
|
7
|
+
|
|
8
|
+
// src/hooks/codex-stop-hook.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import os from "os";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var CODEX_PEEK_MS = clampPeekMs(
|
|
13
|
+
Number.parseInt(process.env["KOJEE_CODEX_PEEK_MS"] ?? "150", 10)
|
|
14
|
+
);
|
|
15
|
+
var MARKER_STALE_MS = 3e4;
|
|
16
|
+
function clampPeekMs(raw) {
|
|
17
|
+
if (!Number.isFinite(raw) || raw <= 0) return 150;
|
|
18
|
+
return Math.min(raw, 500);
|
|
19
|
+
}
|
|
20
|
+
function decideCodexStopHook(deps) {
|
|
21
|
+
if (deps.stopHookActive) return "{}";
|
|
22
|
+
const peek = deps.peekPending();
|
|
23
|
+
if (!peek.pending || peek.cursor === null) return "{}";
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
decision: "block",
|
|
26
|
+
reason: buildCodexWakeReason(peek.cursor)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function codexPendingMarkerPath(sessionId) {
|
|
30
|
+
const safe = (sessionId ?? "no-session").replace(/[^A-Za-z0-9._-]/g, "_");
|
|
31
|
+
return path.join(os.homedir(), ".kojee", `codex-pending-${safe}`);
|
|
32
|
+
}
|
|
33
|
+
function defaultPeekPending(sessionId) {
|
|
34
|
+
const markerPath = codexPendingMarkerPath(sessionId);
|
|
35
|
+
try {
|
|
36
|
+
const st = fs.statSync(markerPath);
|
|
37
|
+
const ageMs = Date.now() - st.mtimeMs;
|
|
38
|
+
if (ageMs > MARKER_STALE_MS) return { pending: false, cursor: null };
|
|
39
|
+
let cursor = 0;
|
|
40
|
+
try {
|
|
41
|
+
const body = fs.readFileSync(markerPath, "utf8").trim().split(/\s+/)[0];
|
|
42
|
+
const n = Number.parseInt(body ?? "", 10);
|
|
43
|
+
if (Number.isFinite(n) && n >= 0) cursor = n;
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
return { pending: true, cursor };
|
|
47
|
+
} catch {
|
|
48
|
+
return { pending: false, cursor: null };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function runCodexStopHook() {
|
|
52
|
+
const { sessionId, stopHookActive } = await readHookStdin();
|
|
53
|
+
const out = decideCodexStopHook({
|
|
54
|
+
stopHookActive,
|
|
55
|
+
peekPending: () => {
|
|
56
|
+
try {
|
|
57
|
+
return defaultPeekPending(sessionId);
|
|
58
|
+
} catch {
|
|
59
|
+
return { pending: false, cursor: null };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
process.stdout.write(out);
|
|
64
|
+
}
|
|
65
|
+
var CODEX_PEEK_BUDGET_MS = CODEX_PEEK_MS;
|
|
66
|
+
export {
|
|
67
|
+
CODEX_PEEK_BUDGET_MS,
|
|
68
|
+
codexPendingMarkerPath,
|
|
69
|
+
decideCodexStopHook,
|
|
70
|
+
defaultPeekPending,
|
|
71
|
+
runCodexStopHook
|
|
72
|
+
};
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-E26AHU6J.js";
|
|
2
|
+
loadPairedConfig
|
|
3
|
+
} from "./chunk-YH27B6SW.js";
|
|
5
4
|
import {
|
|
6
5
|
deriveDiscoveryKey,
|
|
7
6
|
findClaudeAncestorPid
|
|
8
7
|
} from "./chunk-BJMASMKX.js";
|
|
8
|
+
import {
|
|
9
|
+
buildMonitorSpawn,
|
|
10
|
+
buildReplyRecipe
|
|
11
|
+
} from "./chunk-C6GZ2L2W.js";
|
|
9
12
|
import {
|
|
10
13
|
monitorHeartbeatPath,
|
|
11
14
|
statusLogPath
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2TUAFAIW.js";
|
|
13
16
|
import {
|
|
14
17
|
discoveryPathForKey,
|
|
15
18
|
readSessionDiscoveryByKey
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import
|
|
18
|
-
loadPairedConfig
|
|
19
|
-
} from "./chunk-GBOTBYEP.js";
|
|
19
|
+
} from "./chunk-DO42NPNR.js";
|
|
20
|
+
import "./chunk-BLEGIR35.js";
|
|
20
21
|
|
|
21
22
|
// src/doctor.ts
|
|
22
23
|
import fs from "fs";
|
|
@@ -73,13 +74,20 @@ async function collectDoctorReport(deps = {}) {
|
|
|
73
74
|
const discoveryKey = deriveDiscoveryKey(projectDir, ccPid);
|
|
74
75
|
const discoveryPath = discoveryPathForKey(discoveryKey);
|
|
75
76
|
const checks = [];
|
|
76
|
-
const paired = deps.pairedConfigPresent !== void 0 ? deps.pairedConfigPresent : loadPairedConfig() !== null;
|
|
77
|
-
checks.push({
|
|
78
|
-
name: "paired config",
|
|
79
|
-
ok: paired,
|
|
80
|
-
detail: paired ? "present" : "MISSING \u2014 run `kojee-mcp pair <code> --url <broker>`"
|
|
81
|
-
});
|
|
82
77
|
const discovery = readDiscovery(discoveryKey);
|
|
78
|
+
const tokenMode = discovery?.authMode === "token";
|
|
79
|
+
const paired = deps.pairedConfigPresent !== void 0 ? deps.pairedConfigPresent : loadPairedConfig() !== null;
|
|
80
|
+
checks.push(
|
|
81
|
+
tokenMode ? {
|
|
82
|
+
name: "paired config",
|
|
83
|
+
ok: true,
|
|
84
|
+
detail: "n/a (token mode) \u2014 proxy launched with --token; no ~/.kojee/config.json by design"
|
|
85
|
+
} : {
|
|
86
|
+
name: "paired config",
|
|
87
|
+
ok: paired,
|
|
88
|
+
detail: paired ? "present" : "MISSING \u2014 run `kojee-mcp pair <code> --url <broker>`"
|
|
89
|
+
}
|
|
90
|
+
);
|
|
83
91
|
let logPath = null;
|
|
84
92
|
if (!discovery) {
|
|
85
93
|
checks.push({
|
|
@@ -210,6 +218,13 @@ function formatDoctorReport(report) {
|
|
|
210
218
|
return lines.join("\n");
|
|
211
219
|
}
|
|
212
220
|
async function runDoctor() {
|
|
221
|
+
const { readRecordedRuntime } = await import("./runtime-record-WO4IECM6.js");
|
|
222
|
+
if (readRecordedRuntime() === "codex") {
|
|
223
|
+
const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-BMI5JOO6.js");
|
|
224
|
+
const report2 = collectCodexDoctorReport();
|
|
225
|
+
console.error(formatCodexDoctorReport(report2));
|
|
226
|
+
return report2.verdict === "broken" ? 1 : 0;
|
|
227
|
+
}
|
|
213
228
|
const report = await collectDoctorReport();
|
|
214
229
|
console.error(formatDoctorReport(report));
|
|
215
230
|
return report.verdict === "broken" ? 1 : 0;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultCodexConfigPath,
|
|
3
|
+
defaultCodexHooksPath
|
|
4
|
+
} from "./chunk-ZW4SW7LJ.js";
|
|
5
|
+
import "./chunk-SQL56SEB.js";
|
|
6
|
+
import {
|
|
7
|
+
CODEX_LISTEN_CAP_MS
|
|
8
|
+
} from "./chunk-C6GZ2L2W.js";
|
|
9
|
+
import "./chunk-BLEGIR35.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveWebhookConfig
|
|
12
|
+
} from "./chunk-F7L25L2J.js";
|
|
13
|
+
|
|
14
|
+
// src/doctor-codex.ts
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
function parseCodexEnvFromToml(toml) {
|
|
17
|
+
const env = {};
|
|
18
|
+
let inEnv = false;
|
|
19
|
+
for (const line of toml.split("\n")) {
|
|
20
|
+
const header = line.trim();
|
|
21
|
+
const isTableHeader = /^\[\[?[^\]]+\]\]?$/.test(header);
|
|
22
|
+
if (isTableHeader) {
|
|
23
|
+
inEnv = header === "[mcp_servers.kojee.env]";
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!inEnv) continue;
|
|
27
|
+
const eq = line.indexOf("=");
|
|
28
|
+
if (eq <= 0) continue;
|
|
29
|
+
const key = line.slice(0, eq).trim();
|
|
30
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) continue;
|
|
31
|
+
let value = line.slice(eq + 1).trim();
|
|
32
|
+
const quote = value[0];
|
|
33
|
+
if ((quote === '"' || quote === "'") && value.endsWith(quote)) {
|
|
34
|
+
value = value.slice(1, -1);
|
|
35
|
+
if (quote === '"') value = value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
36
|
+
}
|
|
37
|
+
env[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return env;
|
|
40
|
+
}
|
|
41
|
+
var WIZARD_RERUN = "re-run `kojee-mcp init --runtime codex`";
|
|
42
|
+
function defaultReadFile(path) {
|
|
43
|
+
return () => {
|
|
44
|
+
try {
|
|
45
|
+
return fs.readFileSync(path, "utf8");
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function collectCodexDoctorReport(deps = {}) {
|
|
52
|
+
const readConfigToml = deps.readConfigToml ?? defaultReadFile(defaultCodexConfigPath());
|
|
53
|
+
const readHooksJson = deps.readHooksJson ?? defaultReadFile(defaultCodexHooksPath());
|
|
54
|
+
const checks = [];
|
|
55
|
+
const toml = readConfigToml() ?? "";
|
|
56
|
+
const env = deps.env ?? parseCodexEnvFromToml(toml);
|
|
57
|
+
const hasKojeeTable = toml.includes("[mcp_servers.kojee]");
|
|
58
|
+
const hasRuntimeEnv = /KOJEE_RUNTIME\s*=\s*"codex"/.test(toml);
|
|
59
|
+
const configOk = hasKojeeTable && hasRuntimeEnv;
|
|
60
|
+
checks.push({
|
|
61
|
+
name: "~/.codex/config.toml [mcp_servers.kojee]",
|
|
62
|
+
ok: configOk,
|
|
63
|
+
detail: configOk ? 'present with env.KOJEE_RUNTIME="codex" (Tier-1 contract)' : `MISSING [mcp_servers.kojee] or env.KOJEE_RUNTIME="codex" \u2014 ${WIZARD_RERUN}`
|
|
64
|
+
});
|
|
65
|
+
const hooksRaw = readHooksJson() ?? "{}";
|
|
66
|
+
let hookPresent = false;
|
|
67
|
+
try {
|
|
68
|
+
const hooks = JSON.parse(hooksRaw);
|
|
69
|
+
hookPresent = !!hooks.hooks?.Stop?.some(
|
|
70
|
+
(e) => e.hooks?.some((h) => (h.command ?? "").includes("hook --type=codex-stop"))
|
|
71
|
+
);
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
if (!hookPresent && /hook --type=codex-stop/.test(toml)) hookPresent = true;
|
|
75
|
+
checks.push({
|
|
76
|
+
name: "Codex Stop hook (codex-stop)",
|
|
77
|
+
ok: hookPresent,
|
|
78
|
+
detail: hookPresent ? "present (fast PEEK + model-chosen bounded listen)" : `MISSING in ~/.codex/hooks.json / [[hooks.Stop]] \u2014 ${WIZARD_RERUN}`
|
|
79
|
+
});
|
|
80
|
+
const resolution = resolveWebhookConfig(env);
|
|
81
|
+
if (resolution.enabled && resolution.config) {
|
|
82
|
+
checks.push({
|
|
83
|
+
name: "webhook sink",
|
|
84
|
+
ok: true,
|
|
85
|
+
detail: `enabled \u2014 ${resolution.config.redactedSummary}`
|
|
86
|
+
});
|
|
87
|
+
} else if (resolution.error) {
|
|
88
|
+
checks.push({
|
|
89
|
+
name: "webhook sink",
|
|
90
|
+
ok: false,
|
|
91
|
+
detail: `${resolution.error} \u2014 ${WIZARD_RERUN} (it generates a secret)`
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
checks.push({
|
|
95
|
+
name: "webhook sink",
|
|
96
|
+
ok: false,
|
|
97
|
+
detail: `not configured (no KOJEE_WEBHOOK_URL) \u2014 ${WIZARD_RERUN}`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (deps.pingReceiver) {
|
|
101
|
+
const reachable = deps.pingReceiver();
|
|
102
|
+
checks.push({
|
|
103
|
+
name: "webhook receiver (optional ping)",
|
|
104
|
+
ok: reachable ? true : "warn",
|
|
105
|
+
detail: reachable ? "reachable" : "unreachable \u2014 stand up your receiver at KOJEE_WEBHOOK_URL (owner-built; see doctor note)"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const verdict = checks.some((c) => c.ok === false) ? "broken" : checks.some((c) => c.ok === "warn") ? "degraded" : "healthy";
|
|
109
|
+
return { checks, verdict };
|
|
110
|
+
}
|
|
111
|
+
function formatCodexDoctorReport(report) {
|
|
112
|
+
const mark = (ok) => ok === true ? "\u2713" : ok === "warn" ? "\u26A0" : ok === "unknown" ? "?" : "\u2717";
|
|
113
|
+
const lines = [];
|
|
114
|
+
lines.push(`kojee-mcp doctor (codex) \u2014 verdict: ${report.verdict.toUpperCase()}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
lines.push(" Wake mode: webhook-sink + stop-hook peek (Codex has no channel injection).");
|
|
117
|
+
lines.push(` Bounded-listen cap: ${CODEX_LISTEN_CAP_MS}ms (model picks listen vs drain vs ignore).`);
|
|
118
|
+
lines.push("");
|
|
119
|
+
for (const c of report.checks) {
|
|
120
|
+
lines.push(` ${mark(c.ok)} ${c.name}: ${c.detail}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push("NOTE: live Codex verification (hook fires, MCP connects, bounded listen) is an owner step.");
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
collectCodexDoctorReport,
|
|
128
|
+
formatCodexDoctorReport,
|
|
129
|
+
parseCodexEnvFromToml
|
|
130
|
+
};
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
startEventLog,
|
|
6
6
|
statusLogPath,
|
|
7
7
|
sweepStaleEventLogs
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-2TUAFAIW.js";
|
|
9
|
+
import "./chunk-DO42NPNR.js";
|
|
10
|
+
import "./chunk-BLEGIR35.js";
|
|
10
11
|
export {
|
|
11
12
|
STATUS_LINE_PREFIX,
|
|
12
13
|
monitorHeartbeatPath,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,15 @@ interface ProxyConfig {
|
|
|
2
2
|
token: string;
|
|
3
3
|
url: string;
|
|
4
4
|
keystorePath: string;
|
|
5
|
+
/**
|
|
6
|
+
* How credentials were resolved at launch: "token" when `--token` was passed
|
|
7
|
+
* on the CLI, "paired" when read from ~/.kojee/config.json. The proxy records
|
|
8
|
+
* this in its session-discovery file so `kojee-mcp doctor` can render the
|
|
9
|
+
* pairing check honestly on a token-mode box (no config.json by design).
|
|
10
|
+
* Defaults to "paired" when unset (back-compat with callers predating the
|
|
11
|
+
* field).
|
|
12
|
+
*/
|
|
13
|
+
authMode?: "token" | "paired";
|
|
5
14
|
}
|
|
6
15
|
|
|
7
16
|
declare function startProxy(config: ProxyConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startProxy
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-QB22PD6T.js";
|
|
5
|
-
import "./chunk-E26AHU6J.js";
|
|
3
|
+
} from "./chunk-YEC7IHIG.js";
|
|
6
4
|
import "./chunk-BJMASMKX.js";
|
|
5
|
+
import "./chunk-C6GZ2L2W.js";
|
|
6
|
+
import "./chunk-WBMX4CHB.js";
|
|
7
|
+
import "./chunk-BLEGIR35.js";
|
|
7
8
|
export {
|
|
8
9
|
startProxy
|
|
9
10
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
secureFile
|
|
3
|
+
} from "./chunk-BLEGIR35.js";
|
|
4
|
+
|
|
1
5
|
// src/hooks/install.ts
|
|
2
6
|
import fs from "fs";
|
|
3
7
|
import os from "os";
|
|
@@ -48,10 +52,7 @@ function writeConfig(p, cfg) {
|
|
|
48
52
|
const dir = path.dirname(p);
|
|
49
53
|
fs.mkdirSync(dir, { recursive: true });
|
|
50
54
|
fs.writeFileSync(p, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
51
|
-
|
|
52
|
-
fs.chmodSync(p, 384);
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
+
secureFile(p);
|
|
55
56
|
}
|
|
56
57
|
function hasKojeeEntry(arr, command) {
|
|
57
58
|
if (!arr) return false;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearRuntimeRecord,
|
|
3
|
+
readRecordedRuntime,
|
|
4
|
+
recordRuntime,
|
|
5
|
+
runtimeRecordPath
|
|
6
|
+
} from "./chunk-EW72ZNQL.js";
|
|
7
|
+
import "./chunk-SQL56SEB.js";
|
|
8
|
+
import "./chunk-BLEGIR35.js";
|
|
9
|
+
export {
|
|
10
|
+
clearRuntimeRecord,
|
|
11
|
+
readRecordedRuntime,
|
|
12
|
+
recordRuntime,
|
|
13
|
+
runtimeRecordPath
|
|
14
|
+
};
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readHookStdin
|
|
3
3
|
} from "./chunk-LSUB6QMP.js";
|
|
4
|
-
import {
|
|
5
|
-
buildMonitorNudge
|
|
6
|
-
} from "./chunk-E26AHU6J.js";
|
|
7
4
|
import {
|
|
8
5
|
deriveDiscoveryKey,
|
|
9
6
|
findClaudeAncestorPid
|
|
10
7
|
} from "./chunk-BJMASMKX.js";
|
|
8
|
+
import {
|
|
9
|
+
buildMonitorNudge
|
|
10
|
+
} from "./chunk-C6GZ2L2W.js";
|
|
11
11
|
import {
|
|
12
12
|
monitorHeartbeatPath,
|
|
13
13
|
nudgeSentinelPath
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-2TUAFAIW.js";
|
|
15
15
|
import {
|
|
16
16
|
readSessionDiscoveryByKey
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-DO42NPNR.js";
|
|
18
|
+
import "./chunk-BLEGIR35.js";
|
|
18
19
|
|
|
19
20
|
// src/hooks/stop-hook.ts
|
|
20
21
|
import fs from "fs";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAdaptiveWatchdog
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WBMX4CHB.js";
|
|
4
4
|
import {
|
|
5
5
|
STATUS_LINE_PREFIX,
|
|
6
6
|
monitorHeartbeatPath,
|
|
7
7
|
statusLogPath
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-2TUAFAIW.js";
|
|
9
|
+
import "./chunk-DO42NPNR.js";
|
|
10
|
+
import "./chunk-BLEGIR35.js";
|
|
10
11
|
|
|
11
12
|
// src/tail-stream.ts
|
|
12
13
|
import fs from "fs";
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
} from "./chunk-BJMASMKX.js";
|
|
8
8
|
import {
|
|
9
9
|
readSessionDiscoveryByKey
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DO42NPNR.js";
|
|
11
|
+
import "./chunk-BLEGIR35.js";
|
|
11
12
|
|
|
12
13
|
// src/hooks/user-prompt-submit-hook.ts
|
|
13
14
|
async function runUserPromptSubmitHook() {
|