kojee-mcp 0.6.0 → 0.7.1
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 +13 -8
- package/dist/ancestry-A2F5KQ6A.js +10 -0
- package/dist/cc-session-id-RURNIHHC.js +10 -0
- package/dist/{chunk-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
- package/dist/chunk-5DHIUN73.js +25 -0
- package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
- package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
- package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
- package/dist/chunk-D6JKFJ6A.js +11 -0
- package/dist/chunk-E6WMFMM2.js +100 -0
- package/dist/chunk-EBYUJM3H.js +14 -0
- package/dist/chunk-EIAUW6KO.js +82 -0
- package/dist/chunk-EIH2LNF4.js +45 -0
- package/dist/{chunk-BYV7CWNY.js → chunk-FBJCPRVH.js} +22 -9
- package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
- package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
- package/dist/{chunk-ALZX3SGI.js → chunk-GNLCUJBK.js} +13 -27
- package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
- package/dist/chunk-KNEJTD6G.js +31 -0
- package/dist/{chunk-65KRRDHP.js → chunk-KPMD72FY.js} +94 -28
- package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
- package/dist/chunk-NE2F5CKS.js +113 -0
- package/dist/chunk-QJFMU4QC.js +33 -0
- package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
- package/dist/{chunk-Q7T3WAST.js → chunk-SSW5AQSR.js} +6 -1
- package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
- package/dist/chunk-U5HHHRXA.js +103 -0
- package/dist/{setup-handler-7HOFAD7C.js → chunk-UFHGZUST.js} +431 -253
- package/dist/{chunk-N35X22G7.js → chunk-UPJV7GBE.js} +1 -1
- package/dist/chunk-WBXU27BF.js +35 -0
- package/dist/chunk-XJEBJIQE.js +52 -0
- package/dist/chunk-Z5LPNJQ6.js +20 -0
- package/dist/cli.js +63 -25
- package/dist/codex-stop-hook-BMOJVM6O.js +96 -0
- package/dist/connect-handler-4DRTFMOB.js +138 -0
- package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
- package/dist/{doctor-WVX3WCYX.js → doctor-5QJ3HGNR.js} +48 -22
- package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-VGJKTX2E.js} +33 -7
- package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
- package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
- package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
- package/dist/index.js +13 -10
- package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
- package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +11 -8
- package/dist/pair-HTCVSMR6.js +11 -0
- package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
- package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
- package/dist/{registry-YLOFZDI2.js → registry-ZZZ26WGA.js} +197 -24
- package/dist/resolve-slot-BFIB46Y5.js +13 -0
- package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
- package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
- package/dist/{server-R24AVNTI.js → server-ITPFQVTK.js} +5 -2
- package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
- package/dist/setup-handler-JFM45NCN.js +160 -0
- package/dist/{stop-hook-TCPJFGFF.js → stop-hook-5ABGTC2O.js} +17 -20
- package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
- package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
- package/package.json +3 -2
- package/skills/using-tandems/SKILL.md +52 -0
- package/dist/ancestry-ONFBQEP5.js +0 -8
- package/dist/chunk-BLEGIR35.js +0 -43
- package/dist/chunk-CLKCNV2A.js +0 -100
- package/dist/chunk-KFDZWNQO.js +0 -73
- package/dist/chunk-OGHDTFAX.js +0 -50
- package/dist/chunk-VHKPWUX7.js +0 -41
- package/dist/codex-stop-hook-RWQSZTST.js +0 -16
- package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
- package/dist/pair-P4ILCMT7.js +0 -10
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readSessionDiscoveryByKey
|
|
3
|
+
} from "./chunk-67F67AQ6.js";
|
|
4
|
+
import {
|
|
5
|
+
deriveDiscoveryKey,
|
|
6
|
+
findClaudeAncestorPid
|
|
7
|
+
} from "./chunk-XJEBJIQE.js";
|
|
8
|
+
import {
|
|
9
|
+
resolveSharedSessionId
|
|
10
|
+
} from "./chunk-KNEJTD6G.js";
|
|
11
|
+
|
|
12
|
+
// src/hooks/discovery.ts
|
|
13
|
+
async function resolveHookDiscovery(stdinSessionId, deps = {}) {
|
|
14
|
+
const read = deps.readDiscovery ?? readSessionDiscoveryByKey;
|
|
15
|
+
const findPid = deps.findAncestorPid ?? findClaudeAncestorPid;
|
|
16
|
+
const projectDir = deps.projectDir !== void 0 ? deps.projectDir : process.env["CLAUDE_PROJECT_DIR"];
|
|
17
|
+
const sessionId = resolveSharedSessionId({ env: {}, stdinSessionId });
|
|
18
|
+
if (sessionId) {
|
|
19
|
+
const sidKey = deriveDiscoveryKey(projectDir, null, sessionId);
|
|
20
|
+
const sidHit = read(sidKey);
|
|
21
|
+
if (sidHit) return { discovery: sidHit, key: sidKey };
|
|
22
|
+
const ccPid2 = await findPid();
|
|
23
|
+
const legacyKey2 = deriveDiscoveryKey(projectDir, ccPid2, null);
|
|
24
|
+
const legacyHit = read(legacyKey2);
|
|
25
|
+
if (legacyHit) return { discovery: legacyHit, key: legacyKey2 };
|
|
26
|
+
return { discovery: null, key: sidKey };
|
|
27
|
+
}
|
|
28
|
+
const ccPid = await findPid();
|
|
29
|
+
const legacyKey = deriveDiscoveryKey(projectDir, ccPid, null);
|
|
30
|
+
return { discovery: read(legacyKey), key: legacyKey };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
resolveHookDiscovery
|
|
35
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/runtime/ancestry.ts
|
|
2
|
+
import psList from "ps-list";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
var CLAUDE_PROCESS_RE = /\bclaude\b/i;
|
|
5
|
+
function rowLooksLikeClaude(row) {
|
|
6
|
+
return CLAUDE_PROCESS_RE.test(`${row.name} ${row.cmd ?? ""}`);
|
|
7
|
+
}
|
|
8
|
+
async function findClaudeAncestorPid(startPid = process.ppid) {
|
|
9
|
+
let processes;
|
|
10
|
+
try {
|
|
11
|
+
processes = await psList();
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const byPid = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const p of processes) byPid.set(p.pid, p);
|
|
17
|
+
let pid = startPid;
|
|
18
|
+
for (let depth = 0; depth < 20 && pid !== void 0 && pid > 1; depth++) {
|
|
19
|
+
const row = byPid.get(pid);
|
|
20
|
+
if (!row) return null;
|
|
21
|
+
if (rowLooksLikeClaude(row)) return pid;
|
|
22
|
+
if (row.ppid === void 0 || row.ppid === pid) return null;
|
|
23
|
+
pid = row.ppid;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function pidLooksLikeClaude(pid) {
|
|
28
|
+
let processes;
|
|
29
|
+
try {
|
|
30
|
+
processes = await psList();
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const row = processes.find((p) => p.pid === pid);
|
|
35
|
+
if (!row) return null;
|
|
36
|
+
return rowLooksLikeClaude(row);
|
|
37
|
+
}
|
|
38
|
+
function deriveDiscoveryKey(projectDir, ccPid, sessionId) {
|
|
39
|
+
const hasProjectDir = typeof projectDir === "string" && projectDir.length > 0;
|
|
40
|
+
const prefix = createHash("sha256").update(hasProjectDir ? projectDir : "<no-project>").digest("hex").slice(0, 12);
|
|
41
|
+
const sid = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
42
|
+
if (sid) return `${prefix}-s-${sid}`;
|
|
43
|
+
if (!hasProjectDir && ccPid === null) return `orphan-${process.pid}`;
|
|
44
|
+
if (ccPid === null) return `${prefix}-orphan`;
|
|
45
|
+
return `${prefix}-${ccPid}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export {
|
|
49
|
+
findClaudeAncestorPid,
|
|
50
|
+
pidLooksLikeClaude,
|
|
51
|
+
deriveDiscoveryKey
|
|
52
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/tandem/session-id.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { ulid } from "ulidx";
|
|
4
|
+
var MCP_SESSION_ID = ulid();
|
|
5
|
+
function deriveStableSessionId(token, instanceKey) {
|
|
6
|
+
return crypto.createHash("sha256").update(`${token}:${instanceKey}`).digest("hex").slice(0, 32);
|
|
7
|
+
}
|
|
8
|
+
var _stableSessionId = null;
|
|
9
|
+
function setStableSessionId(id) {
|
|
10
|
+
_stableSessionId = id;
|
|
11
|
+
}
|
|
12
|
+
function getSessionId() {
|
|
13
|
+
return _stableSessionId ?? MCP_SESSION_ID;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
deriveStableSessionId,
|
|
18
|
+
setStableSessionId,
|
|
19
|
+
getSessionId
|
|
20
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runPair
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EIAUW6KO.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-FBJCPRVH.js";
|
|
8
|
+
import "./chunk-UPJV7GBE.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-5SZHXYPK.js";
|
|
12
|
+
import "./chunk-247WFMCJ.js";
|
|
13
|
+
import "./chunk-I67C2HYA.js";
|
|
14
|
+
import "./chunk-Z5LPNJQ6.js";
|
|
15
|
+
import "./chunk-MIEI4PLB.js";
|
|
15
16
|
import {
|
|
16
17
|
defaultPairedKeystorePath,
|
|
17
18
|
deriveKeystorePath
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-6G6YYST6.js";
|
|
20
|
+
import "./chunk-U5HHHRXA.js";
|
|
21
|
+
import "./chunk-GNLCUJBK.js";
|
|
20
22
|
import {
|
|
21
23
|
VERSION
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-5DHIUN73.js";
|
|
25
|
+
import "./chunk-SSW5AQSR.js";
|
|
24
26
|
import "./chunk-PPTKGWFF.js";
|
|
25
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-XJEBJIQE.js";
|
|
28
|
+
import "./chunk-KNEJTD6G.js";
|
|
26
29
|
|
|
27
30
|
// src/cli.ts
|
|
28
31
|
import { Command } from "commander";
|
|
@@ -42,17 +45,30 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
|
|
|
42
45
|
process.exit(1);
|
|
43
46
|
}
|
|
44
47
|
});
|
|
48
|
+
program.command("connect <code>").description(
|
|
49
|
+
"Connect this runtime to Kojee with a per-agent pair code from the dashboard (claude-code | codex | openclaw | hermes). claude-code/codex/openclaw write a per-runtime paired slot (~/.kojee/agents/<runtime>/config.json) and point the runtime launcher at it via --paired-config; hermes writes the global ~/.kojee/config.json (its daemon + `kojee-mcp send` read that). Idempotent."
|
|
50
|
+
).requiredOption("--runtime <id>", "Target runtime: claude-code | codex | openclaw | hermes").option("--url <url>", "Broker base URL (default: the canonical staging broker)").action(async (code, opts) => {
|
|
51
|
+
const { runConnect } = await import("./connect-handler-4DRTFMOB.js");
|
|
52
|
+
const result = await runConnect({
|
|
53
|
+
code,
|
|
54
|
+
runtime: opts.runtime,
|
|
55
|
+
...opts.url ? { url: opts.url } : {}
|
|
56
|
+
});
|
|
57
|
+
for (const m of result.messages) console.error(m);
|
|
58
|
+
console.error(result.output);
|
|
59
|
+
process.exit(result.exitCode);
|
|
60
|
+
});
|
|
45
61
|
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
62
|
if (opts.type === "stop") {
|
|
47
|
-
const { runStopHook } = await import("./stop-hook-
|
|
63
|
+
const { runStopHook } = await import("./stop-hook-5ABGTC2O.js");
|
|
48
64
|
await runStopHook();
|
|
49
65
|
process.exit(0);
|
|
50
66
|
} else if (opts.type === "user-prompt-submit") {
|
|
51
|
-
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-
|
|
67
|
+
const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-DTXXFDSD.js");
|
|
52
68
|
await runUserPromptSubmitHook();
|
|
53
69
|
process.exit(0);
|
|
54
70
|
} else if (opts.type === "codex-stop") {
|
|
55
|
-
const { runCodexStopHook } = await import("./codex-stop-hook-
|
|
71
|
+
const { runCodexStopHook } = await import("./codex-stop-hook-BMOJVM6O.js");
|
|
56
72
|
await runCodexStopHook();
|
|
57
73
|
process.exit(0);
|
|
58
74
|
} else {
|
|
@@ -61,16 +77,17 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
|
|
|
61
77
|
}
|
|
62
78
|
});
|
|
63
79
|
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) => {
|
|
64
|
-
const { installHooks, uninstallHooks } = await import("./install-
|
|
80
|
+
const { installHooks, uninstallHooks } = await import("./install-V7LSQCYZ.js");
|
|
65
81
|
if (opts.uninstall) {
|
|
66
82
|
const removed = uninstallHooks({ hooksPath: opts.hooksPath });
|
|
67
83
|
console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
|
|
68
84
|
} else {
|
|
69
85
|
const { stop, ups } = installHooks({ hooksPath: opts.hooksPath });
|
|
86
|
+
const label = (s) => s === "added" ? "\u2713 added" : s === "updated" ? "\u21BB updated" : "\u21BB already installed";
|
|
70
87
|
console.error(
|
|
71
88
|
"Installed kojee hooks in " + (opts.hooksPath ?? "~/.claude/settings.json") + `:
|
|
72
|
-
- Stop: ${stop
|
|
73
|
-
- UserPromptSubmit: ${ups
|
|
89
|
+
- Stop: ${label(stop)}
|
|
90
|
+
- UserPromptSubmit: ${label(ups)}
|
|
74
91
|
Restart Claude Code for hooks to take effect.`
|
|
75
92
|
);
|
|
76
93
|
}
|
|
@@ -78,7 +95,7 @@ Restart Claude Code for hooks to take effect.`
|
|
|
78
95
|
program.command("send <tandem_id>").description(
|
|
79
96
|
"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
97
|
).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-
|
|
98
|
+
const { runSendCli } = await import("./send-cli-45RLGYIC.js");
|
|
82
99
|
const { exitCode, envelope } = await runSendCli({
|
|
83
100
|
tandemId,
|
|
84
101
|
body: opts.body,
|
|
@@ -89,7 +106,7 @@ program.command("send <tandem_id>").description(
|
|
|
89
106
|
process.exit(exitCode);
|
|
90
107
|
});
|
|
91
108
|
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-
|
|
109
|
+
const { runTail } = await import("./tail-stream-NBGHHBS4.js");
|
|
93
110
|
try {
|
|
94
111
|
await runTail(filePath);
|
|
95
112
|
} catch (err) {
|
|
@@ -98,12 +115,12 @@ program.command("tail <path>").description("Stream a file's contents and follow
|
|
|
98
115
|
}
|
|
99
116
|
});
|
|
100
117
|
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-
|
|
118
|
+
const { runDoctor } = await import("./doctor-5QJ3HGNR.js");
|
|
102
119
|
const code = await runDoctor();
|
|
103
120
|
process.exit(code);
|
|
104
121
|
});
|
|
105
122
|
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 (
|
|
123
|
+
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 before configuring (connect runtimes write ~/.kojee/agents/<runtime>/config.json; hermes/openclaw write ~/.kojee/config.json)").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
|
|
107
124
|
"--webhook-signature-format <preset>",
|
|
108
125
|
'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
|
|
109
126
|
).option(
|
|
@@ -117,7 +134,7 @@ function addInstallOptions(cmd, runtimeHelp) {
|
|
|
117
134
|
function makeInstallAction(verb) {
|
|
118
135
|
return async (opts) => {
|
|
119
136
|
const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
|
|
120
|
-
const { runSetup, resolvePairCode } = await import("./setup-handler-
|
|
137
|
+
const { runSetup, resolvePairCode } = await import("./setup-handler-JFM45NCN.js");
|
|
121
138
|
const pairCode = resolvePairCode(opts);
|
|
122
139
|
const result = await runSetup({
|
|
123
140
|
verb,
|
|
@@ -203,6 +220,9 @@ async function promptWebhookUrlFromTty() {
|
|
|
203
220
|
return withReadline((ask) => ask("Webhook receiver URL (press Enter to skip and set it up later):\n> "));
|
|
204
221
|
}
|
|
205
222
|
program.option("--token <token>", "Gateway token (for token-mode)").option("--url <url>", "Broker base URL (for token-mode; required if --token is passed)").option(
|
|
223
|
+
"--paired-config <path>",
|
|
224
|
+
"Explicit per-runtime paired config path (written by `connect`)"
|
|
225
|
+
).option(
|
|
206
226
|
"--keystore-path <path>",
|
|
207
227
|
"Path to keypair file (defaults to per-token path under ~/.kojee/)"
|
|
208
228
|
).action(async (opts) => {
|
|
@@ -211,18 +231,36 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
|
|
|
211
231
|
let keystorePath = opts.keystorePath;
|
|
212
232
|
const authMode = token ? "token" : "paired";
|
|
213
233
|
if (token) {
|
|
234
|
+
if (opts.pairedConfig) {
|
|
235
|
+
console.error(
|
|
236
|
+
"[kojee-mcp] both --token and --paired-config given; using --token (the per-runtime paired config is ignored)."
|
|
237
|
+
);
|
|
238
|
+
}
|
|
214
239
|
if (!url) {
|
|
215
240
|
console.error("--url is required when --token is provided");
|
|
216
241
|
process.exit(1);
|
|
217
242
|
}
|
|
218
243
|
url = url.replace(/\/+$/, "");
|
|
219
244
|
keystorePath ??= deriveKeystorePath(token);
|
|
245
|
+
} else if (opts.pairedConfig) {
|
|
246
|
+
const { resolveLaunchCreds } = await import("./resolve-slot-BFIB46Y5.js");
|
|
247
|
+
const resolved = resolveLaunchCreds({
|
|
248
|
+
pairedConfig: opts.pairedConfig,
|
|
249
|
+
...url ? { url } : {}
|
|
250
|
+
});
|
|
251
|
+
if (!resolved.ok) {
|
|
252
|
+
console.error(resolved.error);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
token = resolved.token;
|
|
256
|
+
url = resolved.url;
|
|
257
|
+
keystorePath = resolved.keystorePath;
|
|
220
258
|
} else {
|
|
221
|
-
const { loadPairedConfig } = await import("./paired-config-
|
|
259
|
+
const { loadPairedConfig } = await import("./paired-config-GUDDO2CH.js");
|
|
222
260
|
const cfg = loadPairedConfig();
|
|
223
261
|
if (!cfg) {
|
|
224
262
|
console.error(
|
|
225
|
-
"No --token provided and no ~/.kojee/config.json found. Run `kojee-mcp
|
|
263
|
+
"No --token provided and no ~/.kojee/config.json found. Run `kojee-mcp connect <code> --runtime <id>` (or `kojee-mcp pair`) first."
|
|
226
264
|
);
|
|
227
265
|
process.exit(1);
|
|
228
266
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readHookStdin
|
|
3
|
+
} from "./chunk-LSUB6QMP.js";
|
|
4
|
+
import {
|
|
5
|
+
codexPendingAckPath,
|
|
6
|
+
codexPendingMarkerPath
|
|
7
|
+
} from "./chunk-EBYUJM3H.js";
|
|
8
|
+
import {
|
|
9
|
+
buildCodexWakeReason
|
|
10
|
+
} from "./chunk-SSW5AQSR.js";
|
|
11
|
+
|
|
12
|
+
// src/hooks/codex-stop-hook.ts
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
var CODEX_PEEK_MS = clampPeekMs(
|
|
16
|
+
Number.parseInt(process.env["KOJEE_CODEX_PEEK_MS"] ?? "150", 10)
|
|
17
|
+
);
|
|
18
|
+
function clampPeekMs(raw) {
|
|
19
|
+
if (!Number.isFinite(raw) || raw <= 0) return 150;
|
|
20
|
+
return Math.min(raw, 500);
|
|
21
|
+
}
|
|
22
|
+
function decideCodexStopHook(deps) {
|
|
23
|
+
if (deps.stopHookActive) return "{}";
|
|
24
|
+
const peek = deps.peekPending();
|
|
25
|
+
if (!peek.pending || peek.cursor === null) return "{}";
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
decision: "block",
|
|
28
|
+
reason: buildCodexWakeReason(peek.cursor)
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function defaultPeekPending() {
|
|
32
|
+
let body;
|
|
33
|
+
try {
|
|
34
|
+
body = fs.readFileSync(codexPendingMarkerPath(), "utf8").trim();
|
|
35
|
+
} catch {
|
|
36
|
+
return { pending: false, cursor: null };
|
|
37
|
+
}
|
|
38
|
+
let acked = null;
|
|
39
|
+
try {
|
|
40
|
+
acked = fs.readFileSync(codexPendingAckPath(), "utf8").trim();
|
|
41
|
+
} catch {
|
|
42
|
+
acked = null;
|
|
43
|
+
}
|
|
44
|
+
if (acked !== null && body === acked) return { pending: false, cursor: null };
|
|
45
|
+
const n = Number.parseInt(body.split(/\s+/)[0] ?? "", 10);
|
|
46
|
+
const cursor = Number.isFinite(n) && n >= 0 ? n : 0;
|
|
47
|
+
return { pending: true, cursor, markerToken: body };
|
|
48
|
+
}
|
|
49
|
+
var MAX_ACK_BODY_CHARS = 128;
|
|
50
|
+
function writeCodexPendingAck(markerToken) {
|
|
51
|
+
const ackPath = codexPendingAckPath();
|
|
52
|
+
const tmp = `${ackPath}.tmp-${process.pid}`;
|
|
53
|
+
try {
|
|
54
|
+
fs.mkdirSync(path.dirname(ackPath), { recursive: true });
|
|
55
|
+
fs.writeFileSync(tmp, markerToken.slice(0, MAX_ACK_BODY_CHARS), { mode: 384 });
|
|
56
|
+
fs.renameSync(tmp, ackPath);
|
|
57
|
+
} catch {
|
|
58
|
+
try {
|
|
59
|
+
fs.unlinkSync(tmp);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function peekDecideAndAck(stopHookActive) {
|
|
65
|
+
let peeked = { pending: false, cursor: null };
|
|
66
|
+
const out = decideCodexStopHook({
|
|
67
|
+
stopHookActive,
|
|
68
|
+
peekPending: () => {
|
|
69
|
+
try {
|
|
70
|
+
peeked = defaultPeekPending();
|
|
71
|
+
} catch {
|
|
72
|
+
peeked = { pending: false, cursor: null };
|
|
73
|
+
}
|
|
74
|
+
return peeked;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
if (!stopHookActive && peeked.pending && peeked.markerToken !== void 0) {
|
|
78
|
+
writeCodexPendingAck(peeked.markerToken);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
async function runCodexStopHook() {
|
|
83
|
+
const { stopHookActive } = await readHookStdin();
|
|
84
|
+
process.stdout.write(peekDecideAndAck(stopHookActive));
|
|
85
|
+
}
|
|
86
|
+
var CODEX_PEEK_BUDGET_MS = CODEX_PEEK_MS;
|
|
87
|
+
export {
|
|
88
|
+
CODEX_PEEK_BUDGET_MS,
|
|
89
|
+
codexPendingAckPath,
|
|
90
|
+
codexPendingMarkerPath,
|
|
91
|
+
decideCodexStopHook,
|
|
92
|
+
defaultPeekPending,
|
|
93
|
+
peekDecideAndAck,
|
|
94
|
+
runCodexStopHook,
|
|
95
|
+
writeCodexPendingAck
|
|
96
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_BROKER_URL,
|
|
3
|
+
reconcileConnectPairSlot,
|
|
4
|
+
runWizard
|
|
5
|
+
} from "./chunk-UFHGZUST.js";
|
|
6
|
+
import "./chunk-E6WMFMM2.js";
|
|
7
|
+
import "./chunk-KPMD72FY.js";
|
|
8
|
+
import "./chunk-QJFMU4QC.js";
|
|
9
|
+
import "./chunk-D6JKFJ6A.js";
|
|
10
|
+
import {
|
|
11
|
+
CONNECT_RUNTIMES
|
|
12
|
+
} from "./chunk-EIH2LNF4.js";
|
|
13
|
+
import "./chunk-RDLF4NQC.js";
|
|
14
|
+
import "./chunk-SQL56SEB.js";
|
|
15
|
+
import "./chunk-77HWBSRH.js";
|
|
16
|
+
import "./chunk-V5VZPYMZ.js";
|
|
17
|
+
import {
|
|
18
|
+
runPair
|
|
19
|
+
} from "./chunk-EIAUW6KO.js";
|
|
20
|
+
import {
|
|
21
|
+
loadPairedConfig,
|
|
22
|
+
pairedConfigPath,
|
|
23
|
+
perRuntimeConfigPath
|
|
24
|
+
} from "./chunk-5SZHXYPK.js";
|
|
25
|
+
import "./chunk-I67C2HYA.js";
|
|
26
|
+
import "./chunk-MIEI4PLB.js";
|
|
27
|
+
import {
|
|
28
|
+
defaultPairedKeystorePath,
|
|
29
|
+
deriveKeystorePath
|
|
30
|
+
} from "./chunk-6G6YYST6.js";
|
|
31
|
+
import "./chunk-U5HHHRXA.js";
|
|
32
|
+
import "./chunk-5DHIUN73.js";
|
|
33
|
+
import "./chunk-SSW5AQSR.js";
|
|
34
|
+
|
|
35
|
+
// src/wizard/connect-handler.ts
|
|
36
|
+
import os from "os";
|
|
37
|
+
import path from "path";
|
|
38
|
+
function absConfigPath(runtime) {
|
|
39
|
+
const p = path.resolve(perRuntimeConfigPath(runtime));
|
|
40
|
+
if (process.platform === "win32" && !/^[a-zA-Z]:/.test(p)) {
|
|
41
|
+
return path.resolve(path.join(os.homedir(), ".kojee", "agents", runtime, "config.json"));
|
|
42
|
+
}
|
|
43
|
+
return p;
|
|
44
|
+
}
|
|
45
|
+
function identityLabel(cfg) {
|
|
46
|
+
if (cfg?.agent_id && cfg?.principal_id) return `${cfg.agent_id} (${cfg.principal_id})`;
|
|
47
|
+
if (cfg?.agent_id) return cfg.agent_id;
|
|
48
|
+
if (cfg?.principal_id) return cfg.principal_id;
|
|
49
|
+
return "this runtime";
|
|
50
|
+
}
|
|
51
|
+
async function runConnect(opts) {
|
|
52
|
+
if (opts.runtime === "hermes") return runConnectHermes(opts);
|
|
53
|
+
const messages = [];
|
|
54
|
+
const runtime = opts.runtime;
|
|
55
|
+
if (!CONNECT_RUNTIMES.includes(runtime)) {
|
|
56
|
+
return {
|
|
57
|
+
messages,
|
|
58
|
+
output: `\`connect\` supports: ${CONNECT_RUNTIMES.join(", ")}, hermes. Unknown --runtime "${runtime}". (claude-code/codex/openclaw write a per-runtime paired slot; hermes writes the global ~/.kojee/config.json.)`,
|
|
59
|
+
exitCode: 2
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const url = (opts.url ?? DEFAULT_BROKER_URL).replace(/\/+$/, "");
|
|
63
|
+
const configPath = absConfigPath(runtime);
|
|
64
|
+
const oldConfig = loadPairedConfig(configPath);
|
|
65
|
+
const reconcile = reconcileConnectPairSlot(runtime, opts.code);
|
|
66
|
+
if (!reconcile.ok) {
|
|
67
|
+
return { messages, output: reconcile.output, exitCode: reconcile.exitCode };
|
|
68
|
+
}
|
|
69
|
+
messages.push(...reconcile.messages);
|
|
70
|
+
const pair = opts.runPair ?? runPair;
|
|
71
|
+
try {
|
|
72
|
+
const r = await pair({
|
|
73
|
+
code: opts.code,
|
|
74
|
+
url,
|
|
75
|
+
keystorePath: deriveKeystorePath(opts.code),
|
|
76
|
+
configPath,
|
|
77
|
+
runtime,
|
|
78
|
+
allowOverwrite: true
|
|
79
|
+
});
|
|
80
|
+
messages.push(r.message);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return { messages, output: `Connect failed while pairing: ${err.message}`, exitCode: 1 };
|
|
83
|
+
}
|
|
84
|
+
const wizard = opts.runWizard ?? runWizard;
|
|
85
|
+
const wiz = await wizard({ runtime, pairedConfigPath: configPath, interactive: false });
|
|
86
|
+
if (wiz.exitCode !== 0) {
|
|
87
|
+
return { messages, output: wiz.output, exitCode: wiz.exitCode };
|
|
88
|
+
}
|
|
89
|
+
const newConfig = loadPairedConfig(configPath);
|
|
90
|
+
const newLabel = identityLabel(newConfig);
|
|
91
|
+
const summary = oldConfig ? `\u21BB re-connected ${runtime} as ${newLabel} (was ${identityLabel(oldConfig)})` : `\u2713 connected ${runtime} as ${newLabel} \u2192 ${url}`;
|
|
92
|
+
const output = `${summary}
|
|
93
|
+
Restart ${runtime} to pick up the connection.`;
|
|
94
|
+
return { messages, output, exitCode: 0 };
|
|
95
|
+
}
|
|
96
|
+
async function runConnectHermes(opts) {
|
|
97
|
+
const messages = [];
|
|
98
|
+
const url = (opts.url ?? DEFAULT_BROKER_URL).replace(/\/+$/, "");
|
|
99
|
+
const configPath = pairedConfigPath();
|
|
100
|
+
const oldConfig = loadPairedConfig(configPath);
|
|
101
|
+
const guard = reconcileConnectPairSlot("hermes", opts.code);
|
|
102
|
+
if (!guard.ok) {
|
|
103
|
+
return { messages, output: guard.output, exitCode: guard.exitCode };
|
|
104
|
+
}
|
|
105
|
+
messages.push(...guard.messages);
|
|
106
|
+
const pair = opts.runPair ?? runPair;
|
|
107
|
+
try {
|
|
108
|
+
const r = await pair({
|
|
109
|
+
code: opts.code,
|
|
110
|
+
url,
|
|
111
|
+
keystorePath: defaultPairedKeystorePath(),
|
|
112
|
+
configPath,
|
|
113
|
+
runtime: "hermes",
|
|
114
|
+
allowOverwrite: true
|
|
115
|
+
// connect is idempotent
|
|
116
|
+
});
|
|
117
|
+
messages.push(r.message);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return { messages, output: `Connect failed while pairing: ${err.message}`, exitCode: 1 };
|
|
120
|
+
}
|
|
121
|
+
const wizard = opts.runWizard ?? runWizard;
|
|
122
|
+
const wiz = await wizard({ runtime: "hermes", interactive: false });
|
|
123
|
+
if (wiz.exitCode !== 0) {
|
|
124
|
+
return { messages, output: wiz.output, exitCode: wiz.exitCode };
|
|
125
|
+
}
|
|
126
|
+
const label = identityLabel(loadPairedConfig(configPath));
|
|
127
|
+
const summary = oldConfig ? `\u21BB re-connected hermes as ${label}` : `\u2713 connected hermes as ${label} \u2192 ${url}`;
|
|
128
|
+
return {
|
|
129
|
+
messages,
|
|
130
|
+
output: `${summary}
|
|
131
|
+
Start (or restart) the hermes daemon to pick up the connection.`,
|
|
132
|
+
exitCode: 0
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
runConnect,
|
|
137
|
+
runConnectHermes
|
|
138
|
+
};
|