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.
Files changed (38) hide show
  1. package/README.md +98 -10
  2. package/dist/chunk-2TUAFAIW.js +244 -0
  3. package/dist/{chunk-36DMIXH7.js → chunk-BJMASMKX.js} +13 -23
  4. package/dist/chunk-BLEGIR35.js +43 -0
  5. package/dist/chunk-C6GZ2L2W.js +38 -0
  6. package/dist/{chunk-VZVGTHGF.js → chunk-DO42NPNR.js} +11 -17
  7. package/dist/chunk-EW72ZNQL.js +39 -0
  8. package/dist/chunk-F7L25L2J.js +60 -0
  9. package/dist/{chunk-WHTH6WBP.js → chunk-LSUB6QMP.js} +3 -0
  10. package/dist/chunk-LVL25VLO.js +22 -0
  11. package/dist/chunk-SQL56SEB.js +14 -0
  12. package/dist/chunk-WBMX4CHB.js +378 -0
  13. package/dist/{chunk-ZGVUM4AG.js → chunk-YEC7IHIG.js} +276 -318
  14. package/dist/{chunk-E7TE4QZD.js → chunk-YH27B6SW.js} +9 -9
  15. package/dist/chunk-ZW4SW7LJ.js +225 -0
  16. package/dist/cli.js +70 -78
  17. package/dist/codex-stop-hook-JOTBCS5K.js +72 -0
  18. package/dist/doctor-TSHOMT5X.js +237 -0
  19. package/dist/doctor-codex-BMI5JOO6.js +130 -0
  20. package/dist/event-log-RSTM4PLL.js +18 -0
  21. package/dist/{hook-server-43QS7L7P.js → hook-server-QF5JVUHV.js} +28 -0
  22. package/dist/index.d.ts +9 -0
  23. package/dist/index.js +5 -2
  24. package/dist/{install-WV25CRU2.js → install-WBIUVBZW.js} +9 -7
  25. package/dist/{paired-config-OAR3O3XY.js → paired-config-JTFLHMZ2.js} +2 -1
  26. package/dist/resubscribe-SLZNA76S.js +59 -0
  27. package/dist/runtime-record-WO4IECM6.js +14 -0
  28. package/dist/runtimes-CO43XUUK.js +12 -0
  29. package/dist/{session-discovery-WSHLR4OV.js → session-discovery-FNMJGFPM.js} +2 -1
  30. package/dist/stop-hook-SEPWWETV.js +119 -0
  31. package/dist/tail-stream-BYKO4DW6.js +162 -0
  32. package/dist/{user-prompt-submit-hook-WSRIJVF4.js → user-prompt-submit-hook-ARPEO6FF.js} +5 -4
  33. package/dist/webhook-config-5TLLX7RA.js +10 -0
  34. package/dist/webhook-sink-7OYZBWXA.js +163 -0
  35. package/dist/wizard-7KHD5JT4.js +265 -0
  36. package/package.json +9 -7
  37. package/dist/event-log-ETWR6PPY.js +0 -112
  38. 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(process.env["HOME"] ?? "~", ".kojee", "config.json");
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
- try {
19
- fs.chmodSync(dir, 448);
20
- } catch {
21
- }
24
+ secureDir(dir);
22
25
  fs.writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
23
- try {
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-E7TE4QZD.js";
11
- import "./chunk-36DMIXH7.js";
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(process.env["HOME"] ?? "~", ".kojee");
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("0.3.0").enablePositionalOptions();
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 or user-prompt-submit").action(async (opts) => {
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-5XU3EQAE.js");
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-WSRIJVF4.js");
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' or 'user-prompt-submit'.`);
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-WV25CRU2.js");
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("init").description("Install kojee into Claude Code (MCP server entry + hooks). Run after `kojee-mcp pair`.").option("--config-path <path>", "Override default ~/.claude.json").option("--uninstall", "Remove the kojee MCP server entry and hooks").action(async (opts) => {
111
- const { loadPairedConfig: loadPairedConfig2 } = await import("./paired-config-OAR3O3XY.js");
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 { runInit, runUninstall } = await import("./install-WV25CRU2.js");
117
- if (opts.uninstall) {
118
- const report = runUninstall({ configPath: opts.configPath });
119
- console.error(formatUninstall(report));
120
- } else {
121
- const report = runInit({ configPath: opts.configPath });
122
- console.error(formatInit(report));
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 formatInit(report) {
126
- const tick = (s) => {
127
- if (s === "added") return "\u2713 added";
128
- if (s === "already-installed") return "\u21BB already installed";
129
- if (s === "preserved-different") return "\u26A0 preserved (existing entry differs \u2014 left untouched)";
130
- if (s === "not-found") return "\u2014 not found";
131
- return s ?? "\u2014";
132
- };
133
- const lines = ["Installing kojee for Claude:"];
134
- for (const t of report.targets) {
135
- const label = t.kind === "cli" ? "CLI" : "Claude.app";
136
- lines.push("");
137
- lines.push(` ${t.path} (${label})`);
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-OAR3O3XY.js");
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
+ };