kojee-mcp 0.4.0 → 0.5.2
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 +98 -10
- package/dist/chunk-2TUAFAIW.js +244 -0
- package/dist/{chunk-36DMIXH7.js → chunk-BJMASMKX.js} +13 -23
- package/dist/chunk-BLEGIR35.js +43 -0
- package/dist/chunk-C6GZ2L2W.js +38 -0
- package/dist/{chunk-VZVGTHGF.js → chunk-DO42NPNR.js} +11 -17
- package/dist/chunk-EW72ZNQL.js +39 -0
- package/dist/chunk-F7L25L2J.js +60 -0
- package/dist/{chunk-WHTH6WBP.js → chunk-LSUB6QMP.js} +3 -0
- package/dist/chunk-LVL25VLO.js +22 -0
- package/dist/chunk-SQL56SEB.js +14 -0
- package/dist/chunk-WBMX4CHB.js +378 -0
- package/dist/{chunk-ZGVUM4AG.js → chunk-YEC7IHIG.js} +276 -318
- package/dist/{chunk-E7TE4QZD.js → chunk-YH27B6SW.js} +9 -9
- package/dist/chunk-ZW4SW7LJ.js +225 -0
- package/dist/cli.js +70 -78
- package/dist/codex-stop-hook-JOTBCS5K.js +72 -0
- package/dist/doctor-TSHOMT5X.js +237 -0
- package/dist/doctor-codex-BMI5JOO6.js +130 -0
- package/dist/event-log-RSTM4PLL.js +18 -0
- package/dist/{hook-server-43QS7L7P.js → hook-server-QF5JVUHV.js} +28 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -2
- package/dist/{install-WV25CRU2.js → install-WBIUVBZW.js} +9 -7
- package/dist/{paired-config-OAR3O3XY.js → paired-config-JTFLHMZ2.js} +2 -1
- package/dist/resubscribe-SLZNA76S.js +59 -0
- package/dist/runtime-record-WO4IECM6.js +14 -0
- package/dist/runtimes-CO43XUUK.js +12 -0
- package/dist/{session-discovery-WSHLR4OV.js → session-discovery-FNMJGFPM.js} +2 -1
- package/dist/stop-hook-SEPWWETV.js +119 -0
- package/dist/tail-stream-BYKO4DW6.js +162 -0
- package/dist/{user-prompt-submit-hook-WSRIJVF4.js → user-prompt-submit-hook-ARPEO6FF.js} +5 -4
- 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 +9 -7
- package/dist/event-log-ETWR6PPY.js +0 -112
- package/dist/stop-hook-5XU3EQAE.js +0 -76
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
secureDir,
|
|
3
|
+
secureFile
|
|
4
|
+
} from "./chunk-BLEGIR35.js";
|
|
5
|
+
|
|
1
6
|
// src/auth/paired-config.ts
|
|
2
7
|
import fs from "fs";
|
|
8
|
+
import os from "os";
|
|
3
9
|
import path from "path";
|
|
4
10
|
function pairedConfigPath() {
|
|
5
|
-
return path.join(
|
|
11
|
+
return path.join(os.homedir(), ".kojee", "config.json");
|
|
6
12
|
}
|
|
7
13
|
function loadPairedConfig(filePath = pairedConfigPath()) {
|
|
8
14
|
try {
|
|
@@ -15,15 +21,9 @@ function loadPairedConfig(filePath = pairedConfigPath()) {
|
|
|
15
21
|
function savePairedConfig(filePath, config) {
|
|
16
22
|
const dir = path.dirname(filePath);
|
|
17
23
|
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
18
|
-
|
|
19
|
-
fs.chmodSync(dir, 448);
|
|
20
|
-
} catch {
|
|
21
|
-
}
|
|
24
|
+
secureDir(dir);
|
|
22
25
|
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
23
|
-
|
|
24
|
-
fs.chmodSync(filePath, 384);
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
26
|
+
secureFile(filePath);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export {
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
kojeeHomeDir
|
|
3
|
+
} from "./chunk-SQL56SEB.js";
|
|
4
|
+
import {
|
|
5
|
+
secureFile
|
|
6
|
+
} from "./chunk-BLEGIR35.js";
|
|
7
|
+
|
|
8
|
+
// src/wizard/codex-config.ts
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
function defaultCodexConfigPath() {
|
|
12
|
+
return path.join(kojeeHomeDir(), ".codex", "config.toml");
|
|
13
|
+
}
|
|
14
|
+
function defaultCodexHooksPath() {
|
|
15
|
+
return path.join(kojeeHomeDir(), ".codex", "hooks.json");
|
|
16
|
+
}
|
|
17
|
+
var CODEX_STOP_HOOK_COMMAND = "npx -y kojee-mcp hook --type=codex-stop";
|
|
18
|
+
function buildCodexMcpServerTable(opts) {
|
|
19
|
+
return [
|
|
20
|
+
"[mcp_servers.kojee]",
|
|
21
|
+
'command = "npx"',
|
|
22
|
+
'args = ["-y", "kojee-mcp"]',
|
|
23
|
+
"",
|
|
24
|
+
"[mcp_servers.kojee.env]",
|
|
25
|
+
'KOJEE_RUNTIME = "codex"',
|
|
26
|
+
`KOJEE_WEBHOOK_URL = "${escapeTomlString(opts.webhookUrl)}"`,
|
|
27
|
+
`KOJEE_WEBHOOK_SECRET = "${escapeTomlString(opts.webhookSecret)}"`
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
30
|
+
function buildCodexStopHookBlock() {
|
|
31
|
+
return [
|
|
32
|
+
"[[hooks.Stop]]",
|
|
33
|
+
"[[hooks.Stop.hooks]]",
|
|
34
|
+
'type = "command"',
|
|
35
|
+
`command = "${escapeTomlString(CODEX_STOP_HOOK_COMMAND)}"`
|
|
36
|
+
].join("\n");
|
|
37
|
+
}
|
|
38
|
+
function escapeTomlString(s) {
|
|
39
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
40
|
+
}
|
|
41
|
+
var KOJEE_TABLE_HEADER = "[mcp_servers.kojee]";
|
|
42
|
+
var KOJEE_ENV_TABLE_HEADER = "[mcp_servers.kojee.env]";
|
|
43
|
+
function writeCodexConfig(inputs) {
|
|
44
|
+
const configPath = inputs.configPath ?? defaultCodexConfigPath();
|
|
45
|
+
const hooksPath = inputs.hooksPath ?? defaultCodexHooksPath();
|
|
46
|
+
let toml = "";
|
|
47
|
+
try {
|
|
48
|
+
toml = fs.readFileSync(configPath, "utf8");
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
toml = upsertKojeeTomlTables(toml, inputs.webhookUrl, inputs.webhookSecret);
|
|
52
|
+
writeFile600(configPath, toml);
|
|
53
|
+
const hooks = readJson(hooksPath);
|
|
54
|
+
hooks.hooks ??= {};
|
|
55
|
+
hooks.hooks.Stop ??= [];
|
|
56
|
+
const already = hooks.hooks.Stop.some(
|
|
57
|
+
(e) => e.hooks?.some((h) => h.command === CODEX_STOP_HOOK_COMMAND)
|
|
58
|
+
);
|
|
59
|
+
if (!already) {
|
|
60
|
+
hooks.hooks.Stop.push({
|
|
61
|
+
hooks: [{ type: "command", command: CODEX_STOP_HOOK_COMMAND }]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
writeFile600(hooksPath, JSON.stringify(hooks, null, 2));
|
|
65
|
+
}
|
|
66
|
+
function removeCodexConfig(opts = {}) {
|
|
67
|
+
const configPath = opts.configPath ?? defaultCodexConfigPath();
|
|
68
|
+
const hooksPath = opts.hooksPath ?? defaultCodexHooksPath();
|
|
69
|
+
const result = { mcpServer: false, stopHook: false };
|
|
70
|
+
try {
|
|
71
|
+
const toml = fs.readFileSync(configPath, "utf8");
|
|
72
|
+
const stripped = stripKojeeTomlTables(toml);
|
|
73
|
+
if (stripped !== toml) {
|
|
74
|
+
result.mcpServer = true;
|
|
75
|
+
writeFile600(configPath, stripped);
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const hooks = readJson(hooksPath);
|
|
81
|
+
const stop = hooks.hooks?.Stop;
|
|
82
|
+
if (stop && stop.length > 0) {
|
|
83
|
+
const before = stop.length;
|
|
84
|
+
hooks.hooks.Stop = stop.filter(
|
|
85
|
+
(e) => !e.hooks?.some((h) => h.command.startsWith("npx -y kojee-mcp hook"))
|
|
86
|
+
);
|
|
87
|
+
if (hooks.hooks.Stop.length !== before) {
|
|
88
|
+
result.stopHook = true;
|
|
89
|
+
writeFile600(hooksPath, JSON.stringify(hooks, null, 2));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function upsertKojeeTomlTables(existing, webhookUrl, webhookSecret) {
|
|
97
|
+
const parsed = extractKojeeBlock(existing);
|
|
98
|
+
if (!parsed) {
|
|
99
|
+
const block = buildCodexMcpServerTable({ webhookUrl, webhookSecret });
|
|
100
|
+
const base2 = existing.replace(/\s*$/, "");
|
|
101
|
+
return base2.length === 0 ? block + "\n" : base2 + "\n\n" + block + "\n";
|
|
102
|
+
}
|
|
103
|
+
const tableKeys = upsertKeyLines(parsed.tableKeys, [
|
|
104
|
+
["command", '"npx"'],
|
|
105
|
+
["args", '["-y", "kojee-mcp"]']
|
|
106
|
+
]);
|
|
107
|
+
const envKeys = upsertKeyLines(parsed.envKeys, [
|
|
108
|
+
["KOJEE_RUNTIME", '"codex"'],
|
|
109
|
+
["KOJEE_WEBHOOK_URL", `"${escapeTomlString(webhookUrl)}"`],
|
|
110
|
+
["KOJEE_WEBHOOK_SECRET", `"${escapeTomlString(webhookSecret)}"`]
|
|
111
|
+
]);
|
|
112
|
+
const merged = [
|
|
113
|
+
KOJEE_TABLE_HEADER,
|
|
114
|
+
...tableKeys.map(([k, v]) => `${k} = ${v}`),
|
|
115
|
+
"",
|
|
116
|
+
KOJEE_ENV_TABLE_HEADER,
|
|
117
|
+
...envKeys.map(([k, v]) => `${k} = ${v}`)
|
|
118
|
+
].join("\n");
|
|
119
|
+
const base = parsed.rest.replace(/\s*$/, "");
|
|
120
|
+
return base.length === 0 ? merged + "\n" : base + "\n\n" + merged + "\n";
|
|
121
|
+
}
|
|
122
|
+
function upsertKeyLines(existing, owned) {
|
|
123
|
+
const ownedKeys = new Set(owned.map(([k]) => k));
|
|
124
|
+
const preserved = existing.filter(([k]) => !ownedKeys.has(k));
|
|
125
|
+
return [...preserved, ...owned];
|
|
126
|
+
}
|
|
127
|
+
function extractKojeeBlock(toml) {
|
|
128
|
+
const lines = toml.split("\n");
|
|
129
|
+
const rest = [];
|
|
130
|
+
const tableKv = /* @__PURE__ */ new Map();
|
|
131
|
+
const envKv = /* @__PURE__ */ new Map();
|
|
132
|
+
let found = false;
|
|
133
|
+
let section = "other";
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
const header = line.trim();
|
|
136
|
+
const isTableHeader = /^\[\[?[^\]]+\]\]?$/.test(header);
|
|
137
|
+
if (isTableHeader) {
|
|
138
|
+
if (header === KOJEE_TABLE_HEADER) {
|
|
139
|
+
found = true;
|
|
140
|
+
section = "table";
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (header === KOJEE_ENV_TABLE_HEADER) {
|
|
144
|
+
found = true;
|
|
145
|
+
section = "env";
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (header.startsWith("[mcp_servers.kojee.")) {
|
|
149
|
+
section = "other";
|
|
150
|
+
rest.push(line);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
section = "other";
|
|
154
|
+
rest.push(line);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (section === "table" || section === "env") {
|
|
158
|
+
const kv = parseTomlKeyValue(line);
|
|
159
|
+
if (kv) {
|
|
160
|
+
(section === "table" ? tableKv : envKv).set(kv[0], kv[1]);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (header.length > 0) rest.push(line);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
rest.push(line);
|
|
167
|
+
}
|
|
168
|
+
if (!found) return null;
|
|
169
|
+
return {
|
|
170
|
+
tableKeys: [...tableKv.entries()],
|
|
171
|
+
envKeys: [...envKv.entries()],
|
|
172
|
+
rest: rest.join("\n")
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function parseTomlKeyValue(line) {
|
|
176
|
+
const trimmed = line.trim();
|
|
177
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
178
|
+
const eq = trimmed.indexOf("=");
|
|
179
|
+
if (eq <= 0) return null;
|
|
180
|
+
const key = trimmed.slice(0, eq).trim();
|
|
181
|
+
const value = trimmed.slice(eq + 1).trim();
|
|
182
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key)) return null;
|
|
183
|
+
return [key, value];
|
|
184
|
+
}
|
|
185
|
+
function stripKojeeTomlTables(toml) {
|
|
186
|
+
const lines = toml.split("\n");
|
|
187
|
+
const out = [];
|
|
188
|
+
let inKojee = false;
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
const header = line.trim();
|
|
191
|
+
const isTableHeader = /^\[\[?[^\]]+\]\]?$/.test(header);
|
|
192
|
+
if (isTableHeader) {
|
|
193
|
+
const isKojee = header === KOJEE_TABLE_HEADER || header === KOJEE_ENV_TABLE_HEADER || header.startsWith("[mcp_servers.kojee.") || header.startsWith("[mcp_servers.kojee]");
|
|
194
|
+
if (isKojee) {
|
|
195
|
+
inKojee = true;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
inKojee = false;
|
|
199
|
+
}
|
|
200
|
+
if (inKojee) continue;
|
|
201
|
+
out.push(line);
|
|
202
|
+
}
|
|
203
|
+
return out.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
204
|
+
}
|
|
205
|
+
function readJson(p) {
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
208
|
+
} catch {
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function writeFile600(p, content) {
|
|
213
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
214
|
+
fs.writeFileSync(p, content, { mode: 384 });
|
|
215
|
+
secureFile(p);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export {
|
|
219
|
+
defaultCodexConfigPath,
|
|
220
|
+
defaultCodexHooksPath,
|
|
221
|
+
buildCodexMcpServerTable,
|
|
222
|
+
buildCodexStopHookBlock,
|
|
223
|
+
writeCodexConfig,
|
|
224
|
+
removeCodexConfig
|
|
225
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
AuthModule,
|
|
4
|
-
startProxy
|
|
5
|
-
} from "./chunk-ZGVUM4AG.js";
|
|
6
2
|
import {
|
|
7
3
|
loadPairedConfig,
|
|
8
4
|
pairedConfigPath,
|
|
9
5
|
savePairedConfig
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import
|
|
6
|
+
} from "./chunk-YH27B6SW.js";
|
|
7
|
+
import {
|
|
8
|
+
AuthModule,
|
|
9
|
+
VERSION,
|
|
10
|
+
startProxy
|
|
11
|
+
} from "./chunk-YEC7IHIG.js";
|
|
12
|
+
import "./chunk-BJMASMKX.js";
|
|
13
|
+
import "./chunk-C6GZ2L2W.js";
|
|
14
|
+
import "./chunk-WBMX4CHB.js";
|
|
15
|
+
import "./chunk-BLEGIR35.js";
|
|
12
16
|
|
|
13
17
|
// src/cli.ts
|
|
14
18
|
import { Command } from "commander";
|
|
15
19
|
import crypto from "crypto";
|
|
20
|
+
import os from "os";
|
|
16
21
|
import path from "path";
|
|
17
22
|
|
|
18
23
|
// src/tandem/pair.ts
|
|
@@ -55,7 +60,7 @@ async function runPair(opts) {
|
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
// src/cli.ts
|
|
58
|
-
var KOJEE_DIR = path.join(
|
|
63
|
+
var KOJEE_DIR = path.join(os.homedir(), ".kojee");
|
|
59
64
|
function deriveKeystorePath(token) {
|
|
60
65
|
const hash = crypto.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
61
66
|
return path.join(KOJEE_DIR, `keypair-${hash}.json`);
|
|
@@ -65,7 +70,7 @@ function defaultPairedKeystorePath() {
|
|
|
65
70
|
}
|
|
66
71
|
var program = new Command().name("kojee-mcp").description(
|
|
67
72
|
"Local MCP proxy for Kojee \u2014 handles DPoP auth, tool discovery, and governance transparently"
|
|
68
|
-
).version(
|
|
73
|
+
).version(VERSION).enablePositionalOptions();
|
|
69
74
|
program.command("pair <code>").description("Pair this machine against Kojee using a pair code from the dashboard").requiredOption("--url <url>", "Broker base URL (e.g. https://rosie-server.kojee.net)").option("--keystore-path <path>", "Path to keypair.json (default ~/.kojee/keypair.json)").action(async (code, opts) => {
|
|
70
75
|
const url = opts.url.replace(/\/+$/, "");
|
|
71
76
|
const keystorePath = opts.keystorePath ?? defaultPairedKeystorePath();
|
|
@@ -78,22 +83,26 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
78
83
|
process.exit(1);
|
|
79
84
|
}
|
|
80
85
|
});
|
|
81
|
-
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) => {
|
|
82
87
|
if (opts.type === "stop") {
|
|
83
|
-
const { runStopHook } = await import("./stop-hook-
|
|
88
|
+
const { runStopHook } = await import("./stop-hook-SEPWWETV.js");
|
|
84
89
|
await runStopHook();
|
|
85
90
|
process.exit(0);
|
|
86
91
|
} else if (opts.type === "user-prompt-submit") {
|
|
87
|
-
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-
|
|
92
|
+
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-ARPEO6FF.js");
|
|
88
93
|
await runUserPromptSubmitHook();
|
|
89
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);
|
|
90
99
|
} else {
|
|
91
|
-
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'.`);
|
|
92
101
|
process.exit(1);
|
|
93
102
|
}
|
|
94
103
|
});
|
|
95
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) => {
|
|
96
|
-
const { installHooks, uninstallHooks } = await import("./install-
|
|
105
|
+
const { installHooks, uninstallHooks } = await import("./install-WBIUVBZW.js");
|
|
97
106
|
if (opts.uninstall) {
|
|
98
107
|
const removed = uninstallHooks({ hooksPath: opts.hooksPath });
|
|
99
108
|
console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
|
|
@@ -107,75 +116,57 @@ Restart Claude Code for hooks to take effect.`
|
|
|
107
116
|
);
|
|
108
117
|
}
|
|
109
118
|
});
|
|
110
|
-
program.command("
|
|
111
|
-
const {
|
|
119
|
+
program.command("tail <path>").description("Stream a file's contents and follow appends (portable replacement for `tail -F`)").action(async (filePath) => {
|
|
120
|
+
const { runTail } = await import("./tail-stream-BYKO4DW6.js");
|
|
121
|
+
try {
|
|
122
|
+
await runTail(filePath);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error("[kojee-mcp tail] Error:", err.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
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 () => {
|
|
129
|
+
const { runDoctor } = await import("./doctor-TSHOMT5X.js");
|
|
130
|
+
const code = await runDoctor();
|
|
131
|
+
process.exit(code);
|
|
132
|
+
});
|
|
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");
|
|
112
137
|
if (loadPairedConfig2() === null && !opts.uninstall) {
|
|
113
138
|
console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init`.");
|
|
114
139
|
process.exit(1);
|
|
115
140
|
}
|
|
116
|
-
const {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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);
|
|
124
155
|
});
|
|
125
|
-
function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
lines.push(` mcpServers.kojee ${tick(t.mcpServer)}`);
|
|
139
|
-
if (t.kind === "cli") {
|
|
140
|
-
if (t.hooksPath) {
|
|
141
|
-
lines.push(` ${t.hooksPath} (hooks)`);
|
|
142
|
-
}
|
|
143
|
-
lines.push(` hooks.Stop ${tick(t.stopHook)}`);
|
|
144
|
-
lines.push(` hooks.UserPromptSubmit ${tick(t.userPromptSubmitHook)}`);
|
|
145
|
-
} else {
|
|
146
|
-
lines.push(` (hooks not applicable for Claude.app agent mode)`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
lines.push("");
|
|
150
|
-
const desktopWritten = report.targets.some((t) => t.kind === "desktop" && t.mcpServer === "added");
|
|
151
|
-
if (desktopWritten) {
|
|
152
|
-
lines.push(
|
|
153
|
-
"Existing Claude.app agent-mode sessions snapshotted the previous config",
|
|
154
|
-
"and won't pick up this change automatically. Start a NEW agent-mode",
|
|
155
|
-
"session (not a resumed one) to use the updated kojee config.",
|
|
156
|
-
""
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
lines.push("To verify: in any new CC session, run /mcp and confirm `kojee` is listed.");
|
|
160
|
-
lines.push(" run /hooks and confirm both Stop and UserPromptSubmit show the kojee entries.");
|
|
161
|
-
lines.push("To remove: `kojee-mcp init --uninstall`");
|
|
162
|
-
return lines.join("\n");
|
|
163
|
-
}
|
|
164
|
-
function formatUninstall(report) {
|
|
165
|
-
const lines = ["Removing kojee from Claude:"];
|
|
166
|
-
for (const t of report.targets) {
|
|
167
|
-
const label = t.kind === "cli" ? "CLI" : "Claude.app";
|
|
168
|
-
lines.push("");
|
|
169
|
-
lines.push(` ${t.path} (${label})`);
|
|
170
|
-
lines.push(` mcpServers.kojee ${t.mcpServer ? "\u2713 removed" : "\u2014 not found"}`);
|
|
171
|
-
if (t.kind === "cli") {
|
|
172
|
-
if (t.hooksPath) {
|
|
173
|
-
lines.push(` ${t.hooksPath} (hooks)`);
|
|
174
|
-
}
|
|
175
|
-
lines.push(` hook entries ${t.hooks ? "\u2713 removed" : "\u2014 not found"}`);
|
|
176
|
-
}
|
|
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();
|
|
177
169
|
}
|
|
178
|
-
return lines.join("\n");
|
|
179
170
|
}
|
|
180
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(
|
|
181
172
|
"--keystore-path <path>",
|
|
@@ -184,6 +175,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
184
175
|
let token = opts.token;
|
|
185
176
|
let url = opts.url;
|
|
186
177
|
let keystorePath = opts.keystorePath;
|
|
178
|
+
const authMode = token ? "token" : "paired";
|
|
187
179
|
if (token) {
|
|
188
180
|
if (!url) {
|
|
189
181
|
console.error("--url is required when --token is provided");
|
|
@@ -192,7 +184,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
192
184
|
url = url.replace(/\/+$/, "");
|
|
193
185
|
keystorePath ??= deriveKeystorePath(token);
|
|
194
186
|
} else {
|
|
195
|
-
const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-
|
|
187
|
+
const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-JTFLHMZ2.js");
|
|
196
188
|
const cfg = loadPairedConfig2();
|
|
197
189
|
if (!cfg) {
|
|
198
190
|
console.error(
|
|
@@ -205,7 +197,7 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
205
197
|
keystorePath ??= defaultPairedKeystorePath();
|
|
206
198
|
}
|
|
207
199
|
try {
|
|
208
|
-
await startProxy({ token, url, keystorePath });
|
|
200
|
+
await startProxy({ token, url, keystorePath, authMode });
|
|
209
201
|
} catch (err) {
|
|
210
202
|
console.error("[kojee-mcp] Fatal error:", err);
|
|
211
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
|
+
};
|