kojee-mcp 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +13 -8
  2. package/dist/ancestry-A2F5KQ6A.js +10 -0
  3. package/dist/cc-session-id-RURNIHHC.js +10 -0
  4. package/dist/{chunk-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
  5. package/dist/{chunk-BYV7CWNY.js → chunk-5H75AEZ2.js} +22 -9
  6. package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
  7. package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
  8. package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
  9. package/dist/chunk-D6JKFJ6A.js +11 -0
  10. package/dist/chunk-E6WMFMM2.js +100 -0
  11. package/dist/chunk-EIAUW6KO.js +82 -0
  12. package/dist/chunk-EIH2LNF4.js +45 -0
  13. package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
  14. package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
  15. package/dist/{setup-handler-7HOFAD7C.js → chunk-GVWYW7MY.js} +247 -246
  16. package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
  17. package/dist/chunk-KNEJTD6G.js +31 -0
  18. package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
  19. package/dist/chunk-NE2F5CKS.js +113 -0
  20. package/dist/{chunk-N35X22G7.js → chunk-OB5T6P24.js} +1 -1
  21. package/dist/{chunk-65KRRDHP.js → chunk-OBIJ6WQP.js} +82 -27
  22. package/dist/{chunk-Q7T3WAST.js → chunk-QEJUNP3X.js} +2 -1
  23. package/dist/chunk-QJFMU4QC.js +33 -0
  24. package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
  25. package/dist/{chunk-ALZX3SGI.js → chunk-SGRVG4HW.js} +8 -3
  26. package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
  27. package/dist/chunk-U5HHHRXA.js +103 -0
  28. package/dist/chunk-WBXU27BF.js +35 -0
  29. package/dist/chunk-WIU5WGDF.js +10 -0
  30. package/dist/chunk-XJEBJIQE.js +52 -0
  31. package/dist/chunk-Z5LPNJQ6.js +20 -0
  32. package/dist/cli.js +62 -25
  33. package/dist/{chunk-KFDZWNQO.js → codex-stop-hook-EOMQD3J4.js} +11 -15
  34. package/dist/connect-handler-HIRM624N.js +137 -0
  35. package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
  36. package/dist/{doctor-WVX3WCYX.js → doctor-PLKLHTJM.js} +48 -22
  37. package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-HARAAH4Y.js} +23 -6
  38. package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
  39. package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
  40. package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
  41. package/dist/index.js +12 -10
  42. package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
  43. package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
  44. package/dist/lib.d.ts +4 -1
  45. package/dist/lib.js +11 -8
  46. package/dist/pair-HTCVSMR6.js +11 -0
  47. package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
  48. package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
  49. package/dist/{registry-YLOFZDI2.js → registry-VRFHKOPP.js} +193 -24
  50. package/dist/resolve-slot-BFIB46Y5.js +13 -0
  51. package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
  52. package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
  53. package/dist/{server-R24AVNTI.js → server-YTHOMA4H.js} +4 -2
  54. package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
  55. package/dist/setup-handler-UHVKSX7E.js +159 -0
  56. package/dist/{stop-hook-TCPJFGFF.js → stop-hook-YI4KRKF2.js} +17 -20
  57. package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
  58. package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
  59. package/package.json +1 -1
  60. package/dist/ancestry-ONFBQEP5.js +0 -8
  61. package/dist/chunk-BLEGIR35.js +0 -43
  62. package/dist/chunk-CLKCNV2A.js +0 -100
  63. package/dist/chunk-OGHDTFAX.js +0 -50
  64. package/dist/chunk-VHKPWUX7.js +0 -41
  65. package/dist/codex-stop-hook-RWQSZTST.js +0 -16
  66. package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
  67. package/dist/pair-P4ILCMT7.js +0 -10
@@ -0,0 +1,103 @@
1
+ // src/fs-retry.ts
2
+ import fs from "fs";
3
+ var RETRYABLE = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
4
+ function sleepSync(ms) {
5
+ if (ms <= 0) return;
6
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
7
+ }
8
+ function withFileOpRetry(op, opts = {}) {
9
+ const tries = opts.tries ?? 5;
10
+ const backoffMs = opts.backoffMs ?? 15;
11
+ let lastErr;
12
+ for (let i = 0; i < tries; i++) {
13
+ try {
14
+ return op();
15
+ } catch (err) {
16
+ const code = err.code;
17
+ if (!code || !RETRYABLE.has(code)) throw err;
18
+ lastErr = err;
19
+ if (i < tries - 1) sleepSync(backoffMs);
20
+ }
21
+ }
22
+ throw lastErr;
23
+ }
24
+ function renameSyncRetry(from, to, opts) {
25
+ withFileOpRetry(() => fs.renameSync(from, to), opts);
26
+ }
27
+ function unlinkSyncRetry(p, opts) {
28
+ try {
29
+ withFileOpRetry(() => fs.unlinkSync(p), opts);
30
+ } catch (err) {
31
+ if (err.code !== "ENOENT") throw err;
32
+ }
33
+ }
34
+
35
+ // src/secure-file.ts
36
+ import fs2 from "fs";
37
+ import os from "os";
38
+ import { execFileSync } from "child_process";
39
+ function isWindows() {
40
+ return process.platform === "win32";
41
+ }
42
+ function secureFile(filePath) {
43
+ if (isWindows()) {
44
+ restrictAclToCurrentUser(filePath, "(F)");
45
+ } else {
46
+ try {
47
+ fs2.chmodSync(filePath, 384);
48
+ } catch {
49
+ }
50
+ }
51
+ }
52
+ function secureDir(dirPath) {
53
+ if (isWindows()) {
54
+ restrictAclToCurrentUser(dirPath, "(OI)(CI)(F)");
55
+ } else {
56
+ try {
57
+ fs2.chmodSync(dirPath, 448);
58
+ } catch {
59
+ }
60
+ }
61
+ }
62
+ function currentUserSid() {
63
+ try {
64
+ const out = execFileSync("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8" });
65
+ const m = out.match(/S-1-[0-9-]+/);
66
+ return m ? m[0] : null;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ function restrictAclToCurrentUser(target, permission) {
72
+ try {
73
+ const sid = currentUserSid();
74
+ const grantee = sid ? `*${sid}` : os.userInfo().username;
75
+ execFileSync("icacls", [target, "/grant:r", `${grantee}:${permission}`], { stdio: "ignore" });
76
+ execFileSync("icacls", [target, "/inheritance:r"], { stdio: "ignore" });
77
+ if (permission.includes("(F)")) {
78
+ try {
79
+ fs2.accessSync(target, fs2.constants.R_OK);
80
+ } catch {
81
+ try {
82
+ execFileSync("icacls", [target, "/grant:r", `${grantee}:${permission}`], { stdio: "ignore" });
83
+ } catch {
84
+ }
85
+ }
86
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ function createSecureFileSync(filePath) {
91
+ unlinkSyncRetry(filePath);
92
+ const fd = fs2.openSync(filePath, "wx", 384);
93
+ fs2.closeSync(fd);
94
+ secureFile(filePath);
95
+ }
96
+
97
+ export {
98
+ renameSyncRetry,
99
+ unlinkSyncRetry,
100
+ secureFile,
101
+ secureDir,
102
+ createSecureFileSync
103
+ };
@@ -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,10 @@
1
+ // src/hooks/codex-pending-path.ts
2
+ import os from "os";
3
+ import path from "path";
4
+ function codexPendingMarkerPath() {
5
+ return path.join(os.homedir(), ".kojee", "codex-pending");
6
+ }
7
+
8
+ export {
9
+ codexPendingMarkerPath
10
+ };
@@ -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,30 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runPair
4
- } from "./chunk-OGHDTFAX.js";
4
+ } from "./chunk-EIAUW6KO.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-BYV7CWNY.js";
8
- import "./chunk-N35X22G7.js";
7
+ } from "./chunk-5H75AEZ2.js";
8
+ import "./chunk-OB5T6P24.js";
9
9
  import {
10
10
  pairedConfigPath
11
- } from "./chunk-YH27B6SW.js";
12
- import "./chunk-KH5YEJIM.js";
13
- import "./chunk-JXMVZEQ7.js";
14
- import "./chunk-NR4Y54OL.js";
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-CH32ELFX.js";
19
- import "./chunk-BLEGIR35.js";
19
+ } from "./chunk-6G6YYST6.js";
20
+ import "./chunk-U5HHHRXA.js";
20
21
  import {
21
22
  VERSION
22
- } from "./chunk-ALZX3SGI.js";
23
- import "./chunk-Q7T3WAST.js";
23
+ } from "./chunk-SGRVG4HW.js";
24
+ import "./chunk-QEJUNP3X.js";
24
25
  import "./chunk-PPTKGWFF.js";
25
- import "./chunk-VHKPWUX7.js";
26
+ import "./chunk-XJEBJIQE.js";
27
+ import "./chunk-KNEJTD6G.js";
26
28
 
27
29
  // src/cli.ts
28
30
  import { Command } from "commander";
@@ -42,17 +44,30 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
42
44
  process.exit(1);
43
45
  }
44
46
  });
47
+ program.command("connect <code>").description(
48
+ "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."
49
+ ).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) => {
50
+ const { runConnect } = await import("./connect-handler-HIRM624N.js");
51
+ const result = await runConnect({
52
+ code,
53
+ runtime: opts.runtime,
54
+ ...opts.url ? { url: opts.url } : {}
55
+ });
56
+ for (const m of result.messages) console.error(m);
57
+ console.error(result.output);
58
+ process.exit(result.exitCode);
59
+ });
45
60
  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
61
  if (opts.type === "stop") {
47
- const { runStopHook } = await import("./stop-hook-TCPJFGFF.js");
62
+ const { runStopHook } = await import("./stop-hook-YI4KRKF2.js");
48
63
  await runStopHook();
49
64
  process.exit(0);
50
65
  } else if (opts.type === "user-prompt-submit") {
51
- const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-FQ43NIHC.js");
66
+ const { runUserPromptSubmitHook } = await import("./user-prompt-submit-hook-DTXXFDSD.js");
52
67
  await runUserPromptSubmitHook();
53
68
  process.exit(0);
54
69
  } else if (opts.type === "codex-stop") {
55
- const { runCodexStopHook } = await import("./codex-stop-hook-RWQSZTST.js");
70
+ const { runCodexStopHook } = await import("./codex-stop-hook-EOMQD3J4.js");
56
71
  await runCodexStopHook();
57
72
  process.exit(0);
58
73
  } else {
@@ -61,16 +76,17 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
61
76
  }
62
77
  });
63
78
  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-LJY2CHKG.js");
79
+ const { installHooks, uninstallHooks } = await import("./install-V7LSQCYZ.js");
65
80
  if (opts.uninstall) {
66
81
  const removed = uninstallHooks({ hooksPath: opts.hooksPath });
67
82
  console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
68
83
  } else {
69
84
  const { stop, ups } = installHooks({ hooksPath: opts.hooksPath });
85
+ const label = (s) => s === "added" ? "\u2713 added" : s === "updated" ? "\u21BB updated" : "\u21BB already installed";
70
86
  console.error(
71
87
  "Installed kojee hooks in " + (opts.hooksPath ?? "~/.claude/settings.json") + `:
72
- - Stop: ${stop === "added" ? "\u2713 added" : "\u21BB already installed"}
73
- - UserPromptSubmit: ${ups === "added" ? "\u2713 added" : "\u21BB already installed"}
88
+ - Stop: ${label(stop)}
89
+ - UserPromptSubmit: ${label(ups)}
74
90
  Restart Claude Code for hooks to take effect.`
75
91
  );
76
92
  }
@@ -78,7 +94,7 @@ Restart Claude Code for hooks to take effect.`
78
94
  program.command("send <tandem_id>").description(
79
95
  "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
96
  ).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-IYJF32PE.js");
97
+ const { runSendCli } = await import("./send-cli-45RLGYIC.js");
82
98
  const { exitCode, envelope } = await runSendCli({
83
99
  tandemId,
84
100
  body: opts.body,
@@ -89,7 +105,7 @@ program.command("send <tandem_id>").description(
89
105
  process.exit(exitCode);
90
106
  });
91
107
  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-4IMXI44S.js");
108
+ const { runTail } = await import("./tail-stream-NBGHHBS4.js");
93
109
  try {
94
110
  await runTail(filePath);
95
111
  } catch (err) {
@@ -98,12 +114,12 @@ program.command("tail <path>").description("Stream a file's contents and follow
98
114
  }
99
115
  });
100
116
  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-WVX3WCYX.js");
117
+ const { runDoctor } = await import("./doctor-PLKLHTJM.js");
102
118
  const code = await runDoctor();
103
119
  process.exit(code);
104
120
  });
105
121
  function addInstallOptions(cmd, runtimeHelp) {
106
- return cmd.option("--runtime <id>", runtimeHelp).option("--config-path <path>", "Override the runtime's MCP-config path").option("--hooks-path <path>", "Override the runtime's hooks-file path").option("--token <token>", "Gateway token \u2014 token mode (writes --token/--url into the MCP config; requires --url)").option("--url <url>", "Broker base URL (token mode / pair URL; default https://rosie-staging.kojee.net)").option("--pair <code>", "Pair code \u2014 runs the pair flow before configuring (alias of --pair-code)").option("--pair-code <code>", "Pair code \u2014 runs the pair flow (writes ~/.kojee/config.json) before configuring").option("--webhook-url <url>", "Webhook receiver URL (codex/hermes/openclaw)").option("--webhook-secret <secret>", "Webhook HMAC secret (generated if omitted)").option(
122
+ 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
123
  "--webhook-signature-format <preset>",
108
124
  'Signature preset: "github" = header X-Hub-Signature-256, value sha256=<hex> (default: bare hex in X-Kojee-Signature)'
109
125
  ).option(
@@ -117,7 +133,7 @@ function addInstallOptions(cmd, runtimeHelp) {
117
133
  function makeInstallAction(verb) {
118
134
  return async (opts) => {
119
135
  const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
120
- const { runSetup, resolvePairCode } = await import("./setup-handler-7HOFAD7C.js");
136
+ const { runSetup, resolvePairCode } = await import("./setup-handler-UHVKSX7E.js");
121
137
  const pairCode = resolvePairCode(opts);
122
138
  const result = await runSetup({
123
139
  verb,
@@ -203,6 +219,9 @@ async function promptWebhookUrlFromTty() {
203
219
  return withReadline((ask) => ask("Webhook receiver URL (press Enter to skip and set it up later):\n> "));
204
220
  }
205
221
  program.option("--token <token>", "Gateway token (for token-mode)").option("--url <url>", "Broker base URL (for token-mode; required if --token is passed)").option(
222
+ "--paired-config <path>",
223
+ "Explicit per-runtime paired config path (written by `connect`)"
224
+ ).option(
206
225
  "--keystore-path <path>",
207
226
  "Path to keypair file (defaults to per-token path under ~/.kojee/)"
208
227
  ).action(async (opts) => {
@@ -211,18 +230,36 @@ program.option("--token <token>", "Gateway token (for token-mode)").option("--ur
211
230
  let keystorePath = opts.keystorePath;
212
231
  const authMode = token ? "token" : "paired";
213
232
  if (token) {
233
+ if (opts.pairedConfig) {
234
+ console.error(
235
+ "[kojee-mcp] both --token and --paired-config given; using --token (the per-runtime paired config is ignored)."
236
+ );
237
+ }
214
238
  if (!url) {
215
239
  console.error("--url is required when --token is provided");
216
240
  process.exit(1);
217
241
  }
218
242
  url = url.replace(/\/+$/, "");
219
243
  keystorePath ??= deriveKeystorePath(token);
244
+ } else if (opts.pairedConfig) {
245
+ const { resolveLaunchCreds } = await import("./resolve-slot-BFIB46Y5.js");
246
+ const resolved = resolveLaunchCreds({
247
+ pairedConfig: opts.pairedConfig,
248
+ ...url ? { url } : {}
249
+ });
250
+ if (!resolved.ok) {
251
+ console.error(resolved.error);
252
+ process.exit(1);
253
+ }
254
+ token = resolved.token;
255
+ url = resolved.url;
256
+ keystorePath = resolved.keystorePath;
220
257
  } else {
221
- const { loadPairedConfig } = await import("./paired-config-JTFLHMZ2.js");
258
+ const { loadPairedConfig } = await import("./paired-config-GUDDO2CH.js");
222
259
  const cfg = loadPairedConfig();
223
260
  if (!cfg) {
224
261
  console.error(
225
- "No --token provided and no ~/.kojee/config.json found. Run `kojee-mcp pair <code> --url <broker>` first."
262
+ "No --token provided and no ~/.kojee/config.json found. Run `kojee-mcp connect <code> --runtime <id>` (or `kojee-mcp pair`) first."
226
263
  );
227
264
  process.exit(1);
228
265
  }
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  readHookStdin
3
3
  } from "./chunk-LSUB6QMP.js";
4
+ import {
5
+ codexPendingMarkerPath
6
+ } from "./chunk-WIU5WGDF.js";
4
7
  import {
5
8
  buildCodexWakeReason
6
- } from "./chunk-Q7T3WAST.js";
9
+ } from "./chunk-QEJUNP3X.js";
7
10
 
8
11
  // src/hooks/codex-stop-hook.ts
9
12
  import fs from "fs";
10
- import os from "os";
11
- import path from "path";
12
13
  var CODEX_PEEK_MS = clampPeekMs(
13
14
  Number.parseInt(process.env["KOJEE_CODEX_PEEK_MS"] ?? "150", 10)
14
15
  );
@@ -26,12 +27,8 @@ function decideCodexStopHook(deps) {
26
27
  reason: buildCodexWakeReason(peek.cursor)
27
28
  });
28
29
  }
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);
30
+ function defaultPeekPending() {
31
+ const markerPath = codexPendingMarkerPath();
35
32
  try {
36
33
  const st = fs.statSync(markerPath);
37
34
  const ageMs = Date.now() - st.mtimeMs;
@@ -49,12 +46,12 @@ function defaultPeekPending(sessionId) {
49
46
  }
50
47
  }
51
48
  async function runCodexStopHook() {
52
- const { sessionId, stopHookActive } = await readHookStdin();
49
+ const { stopHookActive } = await readHookStdin();
53
50
  const out = decideCodexStopHook({
54
51
  stopHookActive,
55
52
  peekPending: () => {
56
53
  try {
57
- return defaultPeekPending(sessionId);
54
+ return defaultPeekPending();
58
55
  } catch {
59
56
  return { pending: false, cursor: null };
60
57
  }
@@ -63,11 +60,10 @@ async function runCodexStopHook() {
63
60
  process.stdout.write(out);
64
61
  }
65
62
  var CODEX_PEEK_BUDGET_MS = CODEX_PEEK_MS;
66
-
67
63
  export {
68
- decideCodexStopHook,
64
+ CODEX_PEEK_BUDGET_MS,
69
65
  codexPendingMarkerPath,
66
+ decideCodexStopHook,
70
67
  defaultPeekPending,
71
- runCodexStopHook,
72
- CODEX_PEEK_BUDGET_MS
68
+ runCodexStopHook
73
69
  };
@@ -0,0 +1,137 @@
1
+ import {
2
+ DEFAULT_BROKER_URL,
3
+ reconcileConnectPairSlot,
4
+ runWizard
5
+ } from "./chunk-GVWYW7MY.js";
6
+ import "./chunk-E6WMFMM2.js";
7
+ import "./chunk-OBIJ6WQP.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-QEJUNP3X.js";
33
+
34
+ // src/wizard/connect-handler.ts
35
+ import os from "os";
36
+ import path from "path";
37
+ function absConfigPath(runtime) {
38
+ const p = path.resolve(perRuntimeConfigPath(runtime));
39
+ if (process.platform === "win32" && !/^[a-zA-Z]:/.test(p)) {
40
+ return path.resolve(path.join(os.homedir(), ".kojee", "agents", runtime, "config.json"));
41
+ }
42
+ return p;
43
+ }
44
+ function identityLabel(cfg) {
45
+ if (cfg?.agent_id && cfg?.principal_id) return `${cfg.agent_id} (${cfg.principal_id})`;
46
+ if (cfg?.agent_id) return cfg.agent_id;
47
+ if (cfg?.principal_id) return cfg.principal_id;
48
+ return "this runtime";
49
+ }
50
+ async function runConnect(opts) {
51
+ if (opts.runtime === "hermes") return runConnectHermes(opts);
52
+ const messages = [];
53
+ const runtime = opts.runtime;
54
+ if (!CONNECT_RUNTIMES.includes(runtime)) {
55
+ return {
56
+ messages,
57
+ 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.)`,
58
+ exitCode: 2
59
+ };
60
+ }
61
+ const url = (opts.url ?? DEFAULT_BROKER_URL).replace(/\/+$/, "");
62
+ const configPath = absConfigPath(runtime);
63
+ const oldConfig = loadPairedConfig(configPath);
64
+ const reconcile = reconcileConnectPairSlot(runtime, opts.code);
65
+ if (!reconcile.ok) {
66
+ return { messages, output: reconcile.output, exitCode: reconcile.exitCode };
67
+ }
68
+ messages.push(...reconcile.messages);
69
+ const pair = opts.runPair ?? runPair;
70
+ try {
71
+ const r = await pair({
72
+ code: opts.code,
73
+ url,
74
+ keystorePath: deriveKeystorePath(opts.code),
75
+ configPath,
76
+ runtime,
77
+ allowOverwrite: true
78
+ });
79
+ messages.push(r.message);
80
+ } catch (err) {
81
+ return { messages, output: `Connect failed while pairing: ${err.message}`, exitCode: 1 };
82
+ }
83
+ const wizard = opts.runWizard ?? runWizard;
84
+ const wiz = await wizard({ runtime, pairedConfigPath: configPath, interactive: false });
85
+ if (wiz.exitCode !== 0) {
86
+ return { messages, output: wiz.output, exitCode: wiz.exitCode };
87
+ }
88
+ const newConfig = loadPairedConfig(configPath);
89
+ const newLabel = identityLabel(newConfig);
90
+ const summary = oldConfig ? `\u21BB re-connected ${runtime} as ${newLabel} (was ${identityLabel(oldConfig)})` : `\u2713 connected ${runtime} as ${newLabel} \u2192 ${url}`;
91
+ const output = `${summary}
92
+ Restart ${runtime} to pick up the connection.`;
93
+ return { messages, output, exitCode: 0 };
94
+ }
95
+ async function runConnectHermes(opts) {
96
+ const messages = [];
97
+ const url = (opts.url ?? DEFAULT_BROKER_URL).replace(/\/+$/, "");
98
+ const configPath = pairedConfigPath();
99
+ const oldConfig = loadPairedConfig(configPath);
100
+ const guard = reconcileConnectPairSlot("hermes", opts.code);
101
+ if (!guard.ok) {
102
+ return { messages, output: guard.output, exitCode: guard.exitCode };
103
+ }
104
+ messages.push(...guard.messages);
105
+ const pair = opts.runPair ?? runPair;
106
+ try {
107
+ const r = await pair({
108
+ code: opts.code,
109
+ url,
110
+ keystorePath: defaultPairedKeystorePath(),
111
+ configPath,
112
+ runtime: "hermes",
113
+ allowOverwrite: true
114
+ // connect is idempotent
115
+ });
116
+ messages.push(r.message);
117
+ } catch (err) {
118
+ return { messages, output: `Connect failed while pairing: ${err.message}`, exitCode: 1 };
119
+ }
120
+ const wizard = opts.runWizard ?? runWizard;
121
+ const wiz = await wizard({ runtime: "hermes", interactive: false });
122
+ if (wiz.exitCode !== 0) {
123
+ return { messages, output: wiz.output, exitCode: wiz.exitCode };
124
+ }
125
+ const label = identityLabel(loadPairedConfig(configPath));
126
+ const summary = oldConfig ? `\u21BB re-connected hermes as ${label}` : `\u2713 connected hermes as ${label} \u2192 ${url}`;
127
+ return {
128
+ messages,
129
+ output: `${summary}
130
+ Start (or restart) the hermes daemon to pick up the connection.`,
131
+ exitCode: 0
132
+ };
133
+ }
134
+ export {
135
+ runConnect,
136
+ runConnectHermes
137
+ };
@@ -3,8 +3,8 @@ import {
3
3
  controlTokenPath,
4
4
  issueControlToken,
5
5
  loadControlToken
6
- } from "./chunk-GI2CKKBL.js";
7
- import "./chunk-BLEGIR35.js";
6
+ } from "./chunk-GKXHKR3B.js";
7
+ import "./chunk-U5HHHRXA.js";
8
8
  export {
9
9
  controlTokenAuthHeaders,
10
10
  controlTokenPath,