kojee-mcp 0.7.5-staging.7 → 0.7.5-staging.8

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.
@@ -1,11 +1,11 @@
1
+ import {
2
+ wrapNpxLauncher
3
+ } from "./chunk-D6JKFJ6A.js";
1
4
  import {
2
5
  buildHookCommand,
3
6
  isKojeeHookCommand,
4
7
  npxHookCommand
5
- } from "./chunk-TMCNB4JH.js";
6
- import {
7
- wrapNpxLauncher
8
- } from "./chunk-D6JKFJ6A.js";
8
+ } from "./chunk-COM6435C.js";
9
9
  import {
10
10
  kojeeHomeDir
11
11
  } from "./chunk-SQL56SEB.js";
@@ -0,0 +1,65 @@
1
+ // src/hooks/hook-command.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ function npxHookCommand(type) {
6
+ return `npx -y kojee-mcp hook --type=${type}`;
7
+ }
8
+ var NPX_CACHE_RE = /[\\/]_npx[\\/]/;
9
+ function resolveCliEntry(moduleUrl = import.meta.url) {
10
+ try {
11
+ const here = fileURLToPath(moduleUrl);
12
+ const candidate = path.join(path.dirname(here), "cli.js");
13
+ if (!fs.existsSync(candidate)) return null;
14
+ if (NPX_CACHE_RE.test(candidate)) return null;
15
+ return candidate;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+ function stableExecPath(execPath = process.execPath) {
21
+ const m = /^(.*)\/Cellar\/node(?:@[^/]+)?\/[^/]+\/bin\/node$/.exec(execPath);
22
+ if (!m || !m[1]) return execPath;
23
+ const alias = path.join(m[1], "bin", "node");
24
+ try {
25
+ if (!fs.existsSync(alias)) return execPath;
26
+ const real = fs.realpathSync(alias);
27
+ return /[\\/]Cellar[\\/]node(@[^/]+)?[\\/]/.test(real) ? alias : execPath;
28
+ } catch {
29
+ return execPath;
30
+ }
31
+ }
32
+ function buildHookCommand(type, opts = {}) {
33
+ const cliEntry = opts.cliEntry !== void 0 ? opts.cliEntry : resolveCliEntry();
34
+ if (!cliEntry) return npxHookCommand(type);
35
+ const execPath = stableExecPath(opts.execPath ?? process.execPath);
36
+ return `"${execPath}" "${cliEntry}" hook --type=${type}`;
37
+ }
38
+ var PINNED_HOOK_RE = /^"[^"]+" "[^"]+" hook --type=(stop|user-prompt-submit|codex-stop|codex-prompt-submit)$/;
39
+ function isKojeeHookCommand(command) {
40
+ if (command.startsWith("npx -y kojee-mcp hook --type=")) return true;
41
+ return PINNED_HOOK_RE.test(command);
42
+ }
43
+ function classifyHookCommand(command, deps = {}) {
44
+ if (command.startsWith("npx -y kojee-mcp hook --type=")) return "npx";
45
+ if (!PINNED_HOOK_RE.test(command)) return "foreign";
46
+ const paths = [...command.matchAll(/"([^"]+)"/g)].map((m) => m[1] ?? "");
47
+ const cliPath = paths[1] ?? "";
48
+ if (NPX_CACHE_RE.test(cliPath)) return "pinned-ephemeral";
49
+ const exists = deps.exists ?? ((p) => {
50
+ try {
51
+ return fs.existsSync(p);
52
+ } catch {
53
+ return true;
54
+ }
55
+ });
56
+ if (cliPath && !exists(cliPath)) return "pinned-broken";
57
+ return "pinned-durable";
58
+ }
59
+
60
+ export {
61
+ npxHookCommand,
62
+ buildHookCommand,
63
+ isKojeeHookCommand,
64
+ classifyHookCommand
65
+ };
@@ -295,9 +295,11 @@ async function startProxy(config) {
295
295
  }
296
296
  console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
297
297
  let server;
298
- const { selectDelivery } = await import("./registry-ACVPVDCS.js");
298
+ const isHeadlessDaemon = !!process.env["KOJEE_HEADLESS"] || !!process.env["KOJEE_WEBHOOK_EXPECTED"];
299
+ const { selectDelivery } = await import("./registry-NLRXVII7.js");
299
300
  const delivery = selectDelivery(adapter.runtime, {
300
301
  supportsChannels: adapter.supportsChannels,
302
+ headless: isHeadlessDaemon,
301
303
  // Per-window delivered mirror for the tandem_pending tool (codex only —
302
304
  // other runtimes' deliveries ignore this even when passed).
303
305
  ...pendingState ? { onDelivered: (e) => pendingState.noteDelivered(e.tandem_id, e.cursor) } : {}
@@ -338,13 +340,12 @@ async function startProxy(config) {
338
340
  }
339
341
  server = createMcpServer(registry, adapter, tandemMembershipCount);
340
342
  }
341
- const isHeadlessDaemon = !!process.env["KOJEE_WEBHOOK_EXPECTED"];
342
343
  if (!isHeadlessDaemon) {
343
344
  process.stdin.on("end", () => shutdown("stdin end"));
344
345
  process.stdin.on("close", () => shutdown("stdin close"));
345
346
  } else {
346
347
  console.error(
347
- "[kojee-mcp] headless daemon (KOJEE_WEBHOOK_EXPECTED=1): stdin EOF ignored; lifecycle owned by the service manager (SIGTERM/SIGINT/SIGHUP)."
348
+ "[kojee-mcp] headless daemon (KOJEE_HEADLESS / KOJEE_WEBHOOK_EXPECTED): stdin EOF ignored; lifecycle owned by the service manager (SIGTERM/SIGINT/SIGHUP)."
348
349
  );
349
350
  }
350
351
  process.on("SIGHUP", () => shutdown("SIGHUP"));
@@ -87,18 +87,22 @@ function createCachedOpenclawWakeResolver(opts = {}) {
87
87
  return resolution;
88
88
  };
89
89
  }
90
- async function postOpenclawWake(target, body, deps = {}) {
90
+ function openclawAgentUrl(wakeUrl) {
91
+ return wakeUrl.replace(/\/wake$/, "/agent");
92
+ }
93
+ async function postOpenclawAgent(target, body, deps = {}) {
91
94
  const doFetch = deps.fetch ?? fetch;
95
+ const url = openclawAgentUrl(target.url);
92
96
  const controller = new AbortController();
93
97
  const timer = setTimeout(() => controller.abort(), deps.timeoutMs ?? 4e3);
94
98
  try {
95
- const res = await doFetch(target.url, {
99
+ const res = await doFetch(url, {
96
100
  method: "POST",
97
101
  headers: { "content-type": "application/json", authorization: `Bearer ${target.token}` },
98
- body: JSON.stringify(body),
102
+ body: JSON.stringify({ name: "Kojee", wakeMode: "now", ...body }),
99
103
  signal: controller.signal
100
104
  });
101
- return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status, error: `wake POST ${res.status}` };
105
+ return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status, error: `agent POST ${res.status}` };
102
106
  } catch (err) {
103
107
  return { ok: false, error: err.message };
104
108
  } finally {
@@ -109,5 +113,5 @@ async function postOpenclawWake(target, body, deps = {}) {
109
113
  export {
110
114
  resolveOpenclawWakeTarget,
111
115
  createCachedOpenclawWakeResolver,
112
- postOpenclawWake
116
+ postOpenclawAgent
113
117
  };
@@ -5,7 +5,7 @@ import {
5
5
  isPlaceholderWebhookUrl,
6
6
  removeCodexConfig,
7
7
  writeCodexConfig
8
- } from "./chunk-T6QANUS2.js";
8
+ } from "./chunk-376ZI3J7.js";
9
9
  import {
10
10
  removeOpenclawMcpServer,
11
11
  writeOpenclawMcpConfig
@@ -422,9 +422,18 @@ function installBundledPayload(opts) {
422
422
  // src/wizard/service.ts
423
423
  import fs5 from "fs";
424
424
  import path5 from "path";
425
+ function shQuote(value) {
426
+ return `'${value.replace(/'/g, `'\\''`)}'`;
427
+ }
428
+ function wrapperEnvEntries(spec) {
429
+ const marker = spec.wrapperEnv ?? { KOJEE_WEBHOOK_EXPECTED: "1" };
430
+ return [["KOJEE_RUNTIME", spec.runtime], ...Object.entries(marker)];
431
+ }
425
432
  function daemonSourceExecCommand(spec) {
426
433
  const target = spec.nodePath ? `${spec.nodePath} ${spec.binPath}` : spec.binPath;
427
- return `set -a; . ${spec.envFile}; set +a; exec ${target}`;
434
+ const withArgs = spec.binArgs && spec.binArgs.length > 0 ? `${target} ${spec.binArgs.map(shQuote).join(" ")}` : target;
435
+ const source = spec.envFile ? `set -a; . ${spec.envFile}; set +a; ` : "";
436
+ return `${source}exec ${withArgs}`;
428
437
  }
429
438
  function xmlEscape(value) {
430
439
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -443,7 +452,7 @@ function systemdUnit(spec) {
443
452
  // otherwise kill the marker too. It lets startProxy tell the "webhook was
444
453
  // meant to be here" daemon apart from the tools-only child (which never sets
445
454
  // it) when the sourced URL is absent → null delivery.
446
- `Environment=KOJEE_RUNTIME=${spec.runtime} KOJEE_WEBHOOK_EXPECTED=1`,
455
+ `Environment=${wrapperEnvEntries(spec).map(([k, v]) => `${k}=${v}`).join(" ")}`,
447
456
  // §2a: source the env file inside a shell wrapper (replaces EnvironmentFile=).
448
457
  `ExecStart=/bin/sh -c '${daemonSourceExecCommand(spec)}'`,
449
458
  "Restart=always",
@@ -478,7 +487,7 @@ function launchdPlist(spec, label) {
478
487
  " </array>",
479
488
  // §2b: KOJEE_WEBHOOK_EXPECTED in the wrapper env (NOT the sourced file).
480
489
  " <key>EnvironmentVariables</key>",
481
- ` <dict><key>KOJEE_RUNTIME</key><string>${xmlEscape(spec.runtime)}</string><key>KOJEE_WEBHOOK_EXPECTED</key><string>1</string></dict>`,
490
+ ` <dict>${wrapperEnvEntries(spec).map(([k, v]) => `<key>${xmlEscape(k)}</key><string>${xmlEscape(v)}</string>`).join("")}</dict>`,
482
491
  " <key>RunAtLoad</key><true/>",
483
492
  " <key>KeepAlive</key><true/>",
484
493
  "</dict>",
@@ -530,6 +539,15 @@ function writeService(platform, spec) {
530
539
  }
531
540
  return plan;
532
541
  }
542
+ function removeService(platform, spec) {
543
+ const plan = planService(platform, spec);
544
+ let removed = false;
545
+ if (plan.unitPath && fs5.existsSync(plan.unitPath)) {
546
+ fs5.unlinkSync(plan.unitPath);
547
+ removed = true;
548
+ }
549
+ return { removed, deactivateCmd: plan.deactivateCmd };
550
+ }
533
551
 
534
552
  // src/wizard/installers/hermes.ts
535
553
  function installHermes(inp) {
@@ -617,6 +635,16 @@ function installOpenclaw(inp) {
617
635
  ...inp.pairedConfigPath ? { pairedConfigPath: inp.pairedConfigPath } : {},
618
636
  ...inp.now ? { now: inp.now } : {}
619
637
  });
638
+ const platform = inp.platform ?? process.platform;
639
+ const svc = !inp.skipService && inp.binPath && inp.homeDir ? writeService(platform, {
640
+ serviceName: "kojee-openclaw",
641
+ binPath: inp.binPath,
642
+ ...inp.nodePath ? { nodePath: inp.nodePath } : {},
643
+ runtime: "openclaw",
644
+ homeDir: inp.homeDir,
645
+ binArgs: ["--paired-config", inp.pairedConfigPath ?? perRuntimeConfigPath("openclaw")],
646
+ wrapperEnv: { KOJEE_HEADLESS: "1" }
647
+ }) : null;
620
648
  const lines = [];
621
649
  if (r.backedUp) {
622
650
  lines.push(
@@ -625,17 +653,22 @@ function installOpenclaw(inp) {
625
653
  );
626
654
  }
627
655
  lines.push(
628
- "Configured runtime: openclaw (MCP server + /wake injector)",
629
- "Wake mode: kojee-mcp runs as OpenClaw's MCP server; Tandem events POST to the local",
630
- " OpenClaw gateway /hooks/wake (Bearer hooks.token) \u2192 the agent wakes.",
631
- ` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee)`,
656
+ "Configured runtime: openclaw (MCP server + standing /hooks/agent wake daemon)",
657
+ "Wake mode: the kojee-openclaw DAEMON holds the Tandem SSE stream and, on an @mention,",
658
+ " POSTs the local OpenClaw gateway /hooks/agent (Bearer hooks.token) \u2192 a FULL",
659
+ " agent run (kojee tools mounted) so the agent can reply via tandem_send.",
660
+ ` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee \u2014 per-run tools; no SSE stream)`,
632
661
  inp.pairedConfigPath ? ` credentials: per-runtime paired slot (${inp.pairedConfigPath})` : ` credentials: shared ~/.kojee/config.json (paired)`,
633
662
  ` hooks token: ${r.hooksTokenGenerated ? "generated + hooks.enabled=true (new)" : "preserved existing"}`,
663
+ svc ? svc.supported ? ` wake daemon: ${svc.unitPath}` : ` wake daemon: manual \u2014 ${svc.note ?? "unsupported platform"}` : " wake daemon: NOT installed (no launch path resolved) \u2014 run connect from the CLI",
634
664
  "",
635
665
  "Next steps (openclaw):",
636
- " - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
637
- " wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
638
- " - Restart the OpenClaw gateway to spawn the kojee MCP server: openclaw gateway restart",
666
+ ...svc && svc.supported ? [
667
+ ` - Start the standing wake daemon: ${svc.activateCmd}`,
668
+ " (it holds the SSE stream so an IDLE agent is still woken on @mentions)."
669
+ ] : svc ? [` - Start the standing wake daemon: ${svc.activateCmd}`] : [],
670
+ " - Ensure the OpenClaw gateway is running (the daemon POSTs /hooks/agent to it).",
671
+ " - Restart the OpenClaw gateway to spawn the kojee MCP tools server: openclaw gateway restart",
639
672
  " - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
640
673
  "",
641
674
  // Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
@@ -650,11 +683,21 @@ function installOpenclaw(inp) {
650
683
  }
651
684
  function uninstallOpenclaw(inp) {
652
685
  const removed = removeOpenclawMcpServer(inp.openclawConfigPath);
686
+ const svc = inp.homeDir ? removeService(inp.platform ?? process.platform, {
687
+ serviceName: "kojee-openclaw",
688
+ binPath: "",
689
+ runtime: "openclaw",
690
+ homeDir: inp.homeDir
691
+ }) : null;
653
692
  const lines = [
654
- "Uninstalling runtime: openclaw (MCP server + /wake injector)",
693
+ "Uninstalling runtime: openclaw (MCP server + wake daemon)",
655
694
  removed ? ` removed mcp.servers.kojee from ${inp.openclawConfigPath} (hooks + siblings preserved)` : ` no mcp.servers.kojee in ${inp.openclawConfigPath} \u2014 nothing to remove`,
695
+ ...svc ? [
696
+ svc.removed ? ` removed the kojee-openclaw wake-daemon unit (stop it: ${svc.deactivateCmd})` : " no kojee-openclaw wake-daemon unit found"
697
+ ] : [],
656
698
  "",
657
699
  "Next steps (openclaw):",
700
+ " - Stop the wake daemon if still running (see the deactivate command above).",
658
701
  " - Restart the OpenClaw gateway: openclaw gateway restart",
659
702
  " - hooks.enabled/token were left intact (you may use hooks for other integrations)."
660
703
  ];
@@ -827,7 +870,7 @@ function whatHappensNext(runtime, opts) {
827
870
  return lines.join("\n");
828
871
  }
829
872
  async function configureClaudeCode(opts) {
830
- const { runInit } = await import("./install-JQNDGAAQ.js");
873
+ const { runInit } = await import("./install-NG366AOQ.js");
831
874
  const report = runInit({
832
875
  ...opts.configPath ? { configPath: opts.configPath } : {},
833
876
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
@@ -1086,18 +1129,35 @@ function openclawConfigPath(opts) {
1086
1129
  }
1087
1130
  function configureOpenclaw(opts) {
1088
1131
  const runtime = "openclaw";
1132
+ const daemonExec = resolveDaemonExec();
1089
1133
  const install = installOpenclaw({
1090
1134
  openclawConfigPath: openclawConfigPath(opts),
1091
- ...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {}
1135
+ ...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {},
1136
+ // Launch coords for the standing kojee-openclaw wake daemon (holds the SSE
1137
+ // stream so an idle agent is reachable). Absent → installer writes only the
1138
+ // MCP config (the shape tests).
1139
+ binPath: daemonExec.binPath,
1140
+ nodePath: daemonExec.nodePath,
1141
+ homeDir: kojeeHomeDir(),
1142
+ platform: process.platform
1092
1143
  });
1093
1144
  recordRuntime(runtime);
1094
- return { runtime, output: install.output, exitCode: install.exitCode };
1145
+ const lines = [install.output];
1146
+ if (daemonExec.ephemeral) {
1147
+ lines.push(
1148
+ "",
1149
+ "\u26A0 DURABILITY: this ran from an ephemeral npx cache, so the wake daemon points at a",
1150
+ " path npx will eventually delete (the daemon would then fail to start). For a durable",
1151
+ " daemon: npm i -g kojee-mcp && kojee-mcp connect <code> --runtime openclaw"
1152
+ );
1153
+ }
1154
+ return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
1095
1155
  }
1096
1156
  async function runWizardUninstall(runtime, opts) {
1097
1157
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
1098
1158
  const lines = [`Uninstalling runtime: ${effective}`];
1099
1159
  if (effective === "claude-code") {
1100
- const { runUninstall } = await import("./install-JQNDGAAQ.js");
1160
+ const { runUninstall } = await import("./install-NG366AOQ.js");
1101
1161
  const report = runUninstall({
1102
1162
  ...opts.configPath ? { configPath: opts.configPath } : {},
1103
1163
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
@@ -1121,7 +1181,11 @@ async function runWizardUninstall(runtime, opts) {
1121
1181
  lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
1122
1182
  lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
1123
1183
  } else if (effective === "openclaw") {
1124
- const un = uninstallOpenclaw({ openclawConfigPath: openclawConfigPath(opts) });
1184
+ const un = uninstallOpenclaw({
1185
+ openclawConfigPath: openclawConfigPath(opts),
1186
+ homeDir: kojeeHomeDir(),
1187
+ platform: process.platform
1188
+ });
1125
1189
  lines.push(un.output);
1126
1190
  } else {
1127
1191
  const hermesConfig = defaultHermesConfigPath(kojeeHomeDir());
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-EIAUW6KO.js";
5
5
  import {
6
6
  startProxy
7
- } from "./chunk-TUDC4GY5.js";
7
+ } from "./chunk-HRDASFRF.js";
8
8
  import "./chunk-XYDNRVOU.js";
9
9
  import {
10
10
  pairedConfigPath
@@ -47,7 +47,7 @@ program.command("pair <code>").description("Pair this machine against Kojee usin
47
47
  program.command("connect <code>").description(
48
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
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-W7EAIU4R.js");
50
+ const { runConnect } = await import("./connect-handler-NJ4FZIAM.js");
51
51
  const result = await runConnect({
52
52
  code,
53
53
  runtime: opts.runtime,
@@ -57,6 +57,15 @@ program.command("connect <code>").description(
57
57
  console.error(result.output);
58
58
  process.exit(result.exitCode);
59
59
  });
60
+ program.command("reconnect").description(
61
+ "Re-run configure for an already-paired runtime, KEEPING the token + enrolled key (no re-pair). Re-writes configs/hooks to this install and refreshes the agent instructions."
62
+ ).option("--runtime <id>", "Target runtime (defaults to the recorded runtime from the last connect/init)").action(async (opts) => {
63
+ const { runReconnect } = await import("./connect-handler-NJ4FZIAM.js");
64
+ const result = await runReconnect({ ...opts.runtime ? { runtime: opts.runtime } : {} });
65
+ for (const m of result.messages) console.error(m);
66
+ console.error(result.output);
67
+ process.exit(result.exitCode);
68
+ });
60
69
  program.command("hook").description("Run a kojee MCP hook script (called by Claude Code via ~/.claude/settings.json)").requiredOption(
61
70
  "--type <type>",
62
71
  "Hook type: stop, user-prompt-submit, codex-stop, or codex-prompt-submit"
@@ -85,7 +94,7 @@ program.command("hook").description("Run a kojee MCP hook script (called by Clau
85
94
  }
86
95
  });
87
96
  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) => {
88
- const { installHooks, uninstallHooks } = await import("./install-JQNDGAAQ.js");
97
+ const { installHooks, uninstallHooks } = await import("./install-NG366AOQ.js");
89
98
  if (opts.uninstall) {
90
99
  const removed = uninstallHooks({ hooksPath: opts.hooksPath });
91
100
  console.error(removed ? "Removed kojee hook entries." : "No kojee hook entries found.");
@@ -126,7 +135,7 @@ program.command("tail <path>").description("Stream a file's contents and follow
126
135
  }
127
136
  });
128
137
  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-O7MLJEMO.js");
138
+ const { runDoctor } = await import("./doctor-KMYKRYPE.js");
130
139
  const code = await runDoctor();
131
140
  process.exit(code);
132
141
  });
@@ -145,7 +154,7 @@ function addInstallOptions(cmd, runtimeHelp) {
145
154
  function makeInstallAction(verb) {
146
155
  return async (opts) => {
147
156
  const interactive = process.stdin.isTTY === true && opts.runtime === void 0;
148
- const { runSetup, resolvePairCode } = await import("./setup-handler-6T7Q6DYQ.js");
157
+ const { runSetup, resolvePairCode } = await import("./setup-handler-I7ZEZTKW.js");
149
158
  const pairCode = resolvePairCode(opts);
150
159
  const result = await runSetup({
151
160
  verb,
@@ -2,17 +2,19 @@ import {
2
2
  DEFAULT_BROKER_URL,
3
3
  reconcileConnectPairSlot,
4
4
  runWizard
5
- } from "./chunk-RTYRJAJO.js";
6
- import "./chunk-T6QANUS2.js";
5
+ } from "./chunk-ZUF47CPP.js";
6
+ import "./chunk-376ZI3J7.js";
7
7
  import "./chunk-E6WMFMM2.js";
8
8
  import "./chunk-GEGUWQYT.js";
9
9
  import "./chunk-77HWBSRH.js";
10
- import "./chunk-TMCNB4JH.js";
11
10
  import "./chunk-D6JKFJ6A.js";
11
+ import "./chunk-COM6435C.js";
12
12
  import {
13
13
  CONNECT_RUNTIMES
14
14
  } from "./chunk-EIH2LNF4.js";
15
- import "./chunk-RDLF4NQC.js";
15
+ import {
16
+ readRecordedRuntime
17
+ } from "./chunk-RDLF4NQC.js";
16
18
  import "./chunk-SQL56SEB.js";
17
19
  import "./chunk-V5VZPYMZ.js";
18
20
  import {
@@ -33,6 +35,7 @@ import "./chunk-U5HHHRXA.js";
33
35
  import "./chunk-O4JQY7IF.js";
34
36
 
35
37
  // src/wizard/connect-handler.ts
38
+ import fs from "fs";
36
39
  import os from "os";
37
40
  import path from "path";
38
41
  function absConfigPath(runtime) {
@@ -139,7 +142,63 @@ Start (or restart) the hermes daemon to pick up the connection.`,
139
142
  exitCode: 0
140
143
  };
141
144
  }
145
+ async function runReconnect(opts = {}) {
146
+ const messages = [];
147
+ const runtime = opts.runtime ?? readRecordedRuntime() ?? null;
148
+ if (!runtime) {
149
+ return {
150
+ messages,
151
+ output: `reconnect: no runtime recorded on this box \u2014 pass --runtime (${CONNECT_RUNTIMES.join(" | ")} | hermes), or run \`kojee-mcp connect <code> --runtime <id>\` first.`,
152
+ exitCode: 2
153
+ };
154
+ }
155
+ const isHermes = runtime === "hermes";
156
+ if (!isHermes && !CONNECT_RUNTIMES.includes(runtime)) {
157
+ return {
158
+ messages,
159
+ output: `reconnect supports: ${CONNECT_RUNTIMES.join(", ")}, hermes. Unknown --runtime "${runtime}".`,
160
+ exitCode: 2
161
+ };
162
+ }
163
+ const configPath = isHermes ? pairedConfigPath() : absConfigPath(runtime);
164
+ const paired = loadPairedConfig(configPath);
165
+ if (!paired?.token) {
166
+ return {
167
+ messages,
168
+ output: `reconnect: no paired config at ${configPath} \u2014 nothing to keep. Run \`kojee-mcp connect <code> --runtime ${runtime}\` to pair first.`,
169
+ exitCode: 2
170
+ };
171
+ }
172
+ const keystorePath = isHermes ? defaultPairedKeystorePath() : deriveKeystorePath(paired.token);
173
+ if (!fs.existsSync(keystorePath)) {
174
+ messages.push(
175
+ `\u26A0 enrolled keystore MISSING at ${keystorePath} \u2014 configs will be refreshed, but the proxy cannot authenticate with a new key on this token (backend rejects double-enroll). If sends fail, re-pair with a fresh code.`
176
+ );
177
+ }
178
+ const wizard = opts.runWizard ?? runWizard;
179
+ const wiz = await wizard({
180
+ runtime,
181
+ ...isHermes ? {} : { pairedConfigPath: configPath },
182
+ interactive: false
183
+ });
184
+ if (wiz.exitCode !== 0) {
185
+ return { messages, output: wiz.output, exitCode: wiz.exitCode };
186
+ }
187
+ const label = identityLabel(paired);
188
+ return {
189
+ messages,
190
+ output: [
191
+ `\u21BB reconfigured ${runtime} as ${label} \u2014 token KEPT (no re-pair, no key re-enrollment).`,
192
+ "Configs + hooks re-written to this install; agent instructions refreshed below.",
193
+ `Restart ${runtime} to pick up the changes.`,
194
+ "",
195
+ wiz.output
196
+ ].join("\n"),
197
+ exitCode: 0
198
+ };
199
+ }
142
200
  export {
143
201
  runConnect,
144
- runConnectHermes
202
+ runConnectHermes,
203
+ runReconnect
145
204
  };
@@ -1,3 +1,7 @@
1
+ import {
2
+ classifyHookCommand,
3
+ isKojeeHookCommand
4
+ } from "./chunk-COM6435C.js";
1
5
  import "./chunk-XLKGPGZT.js";
2
6
  import {
3
7
  loadControlToken
@@ -149,6 +153,53 @@ async function resolveDefaultPairedScope() {
149
153
  }
150
154
  return { scope: resolution.scope, present, ...runtime ? { runtime } : {} };
151
155
  }
156
+ function defaultReadHookCommands() {
157
+ try {
158
+ const p = path.join(os.homedir(), ".claude", "settings.json");
159
+ const raw = fs.readFileSync(p, "utf8");
160
+ const cfg = JSON.parse(raw);
161
+ const out = [];
162
+ for (const event of ["Stop", "UserPromptSubmit"]) {
163
+ for (const matcher of cfg.hooks?.[event] ?? []) {
164
+ for (const h of matcher.hooks ?? []) {
165
+ if (typeof h.command === "string" && isKojeeHookCommand(h.command)) out.push(h.command);
166
+ }
167
+ }
168
+ }
169
+ return out;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ var HOOK_REPIN_FIX = "fix: `npm i -g kojee-mcp` then re-run `kojee-mcp setup` (re-pins in place, no re-pair)";
175
+ function hookPinCheck(commands) {
176
+ const name = "hook pin (settings.json Stop/UserPromptSubmit)";
177
+ if (commands === null) {
178
+ return { name, ok: "warn", detail: `no ~/.claude/settings.json readable \u2014 run \`kojee-mcp setup\` to install the wake hooks` };
179
+ }
180
+ if (commands.length === 0) {
181
+ return { name, ok: "warn", detail: `no kojee hooks installed \u2014 run \`kojee-mcp setup\` (the stop-hook is the Monitor self-heal)` };
182
+ }
183
+ const states = commands.map((c) => classifyHookCommand(c));
184
+ if (states.includes("pinned-broken")) {
185
+ return { name, ok: false, detail: `pinned cli.js is GONE \u2014 hooks are failing with exit 127 RIGHT NOW; ${HOOK_REPIN_FIX}` };
186
+ }
187
+ if (states.includes("pinned-ephemeral")) {
188
+ return {
189
+ name,
190
+ ok: false,
191
+ detail: `pinned INTO the npx cache (~/.npm/_npx/\u2026) \u2014 npx cache GC will silently kill the hooks (exit 127 every turn); ${HOOK_REPIN_FIX}`
192
+ };
193
+ }
194
+ if (states.includes("npx")) {
195
+ return {
196
+ name,
197
+ ok: "warn",
198
+ detail: `unpinned (npx -y) \u2014 pays npx resolution every turn and can stall on a registry fetch; ${HOOK_REPIN_FIX}`
199
+ };
200
+ }
201
+ return { name, ok: true, detail: `pinned to a durable binary (${commands.length} hook${commands.length === 1 ? "" : "s"})` };
202
+ }
152
203
  async function collectDoctorReport(deps = {}) {
153
204
  const fetchFn = deps.fetchFn ?? fetch;
154
205
  const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
@@ -183,6 +234,8 @@ async function collectDoctorReport(deps = {}) {
183
234
  detail: pairedScope.present ? "present (legacy global slot)" : "MISSING \u2014 run `kojee-mcp connect <code> --runtime <id>` (or `kojee-mcp pair`)"
184
235
  });
185
236
  }
237
+ const hookCommands = (deps.readHookCommands ?? defaultReadHookCommands)();
238
+ checks.push(hookPinCheck(hookCommands));
186
239
  let logPath = null;
187
240
  if (!discovery) {
188
241
  checks.push({
@@ -354,13 +407,13 @@ function formatDoctorReport(report) {
354
407
  async function runDoctor() {
355
408
  const { readRecordedRuntime } = await import("./runtime-record-OXRLTOLC.js");
356
409
  if (readRecordedRuntime() === "codex") {
357
- const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-D4BOWIAA.js");
410
+ const { collectCodexDoctorReport, formatCodexDoctorReport } = await import("./doctor-codex-HB442SFA.js");
358
411
  const report2 = collectCodexDoctorReport();
359
412
  console.error(formatCodexDoctorReport(report2));
360
413
  return report2.verdict === "broken" ? 1 : 0;
361
414
  }
362
415
  if (readRecordedRuntime() === "openclaw") {
363
- const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-LL6NSPLC.js");
416
+ const { collectOpenclawDoctorReport, formatOpenclawDoctorReport } = await import("./doctor-openclaw-IWY64I2E.js");
364
417
  const report2 = collectOpenclawDoctorReport();
365
418
  console.error(formatOpenclawDoctorReport(report2));
366
419
  return report2.verdict === "broken" ? 1 : 0;
@@ -2,9 +2,12 @@ import {
2
2
  defaultCodexConfigPath,
3
3
  defaultCodexHooksPath,
4
4
  isPlaceholderWebhookUrl
5
- } from "./chunk-T6QANUS2.js";
6
- import "./chunk-TMCNB4JH.js";
5
+ } from "./chunk-376ZI3J7.js";
7
6
  import "./chunk-D6JKFJ6A.js";
7
+ import {
8
+ classifyHookCommand,
9
+ isKojeeHookCommand
10
+ } from "./chunk-COM6435C.js";
8
11
  import {
9
12
  CODEX_STATUS_STALE_MS,
10
13
  readAllCodexStatusRaw,
@@ -173,6 +176,39 @@ function collectCodexDoctorReport(deps = {}) {
173
176
  ok: upsPresent ? true : "warn",
174
177
  detail: upsPresent ? "present (pending-wake catch-up at the next prompt)" : `MISSING \u2014 a silently-dropped Stop block is never re-surfaced. ${WIZARD_RERUN}`
175
178
  });
179
+ const codexHookCommands = [];
180
+ for (const event of ["Stop", "UserPromptSubmit"]) {
181
+ for (const e of hooksJson.hooks?.[event] ?? []) {
182
+ for (const h of e.hooks ?? []) {
183
+ if (typeof h.command === "string" && isKojeeHookCommand(h.command)) codexHookCommands.push(h.command);
184
+ }
185
+ }
186
+ }
187
+ if (codexHookCommands.length > 0) {
188
+ const states = codexHookCommands.map((c) => classifyHookCommand(c));
189
+ const repin = "fix: `npm i -g kojee-mcp` then re-run connect for codex (re-pins in place, no re-pair)";
190
+ if (states.includes("pinned-broken")) {
191
+ checks.push({
192
+ name: "hook pin (hooks.json)",
193
+ ok: false,
194
+ detail: `pinned cli.js is GONE \u2014 hooks fail with exit 127 RIGHT NOW; ${repin}`
195
+ });
196
+ } else if (states.includes("pinned-ephemeral")) {
197
+ checks.push({
198
+ name: "hook pin (hooks.json)",
199
+ ok: false,
200
+ detail: `pinned INTO the npx cache (~/.npm/_npx/\u2026) \u2014 cache GC will silently kill the hooks (exit 127 every turn); ${repin}`
201
+ });
202
+ } else if (states.includes("npx")) {
203
+ checks.push({
204
+ name: "hook pin (hooks.json)",
205
+ ok: "warn",
206
+ detail: `unpinned (npx -y) \u2014 pays npx resolution every turn and can stall on a registry fetch; ${repin}`
207
+ });
208
+ } else {
209
+ checks.push({ name: "hook pin (hooks.json)", ok: true, detail: "pinned to a durable binary" });
210
+ }
211
+ }
176
212
  const featureHooks = parseCodexFeatureHooks(toml);
177
213
  if (featureHooks !== true) {
178
214
  checks.push({
@@ -5,7 +5,7 @@ import {
5
5
  import "./chunk-D6JKFJ6A.js";
6
6
  import {
7
7
  resolveOpenclawWakeTarget
8
- } from "./chunk-NE2F5CKS.js";
8
+ } from "./chunk-LKGKDZSO.js";
9
9
  import "./chunk-SQL56SEB.js";
10
10
  import {
11
11
  loadPairedConfig,
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  listTandemIds,
3
3
  startProxy
4
- } from "./chunk-TUDC4GY5.js";
4
+ } from "./chunk-HRDASFRF.js";
5
5
  import "./chunk-XYDNRVOU.js";
6
6
  import "./chunk-TBVJOXIR.js";
7
7
  import "./chunk-Z5LPNJQ6.js";
@@ -1,10 +1,10 @@
1
- import {
2
- buildHookCommand,
3
- isKojeeHookCommand
4
- } from "./chunk-TMCNB4JH.js";
5
1
  import {
6
2
  wrapNpxLauncher
7
3
  } from "./chunk-D6JKFJ6A.js";
4
+ import {
5
+ buildHookCommand,
6
+ isKojeeHookCommand
7
+ } from "./chunk-COM6435C.js";
8
8
  import {
9
9
  secureFile
10
10
  } from "./chunk-U5HHHRXA.js";
@@ -5,8 +5,8 @@ import {
5
5
  } from "./chunk-XLRF5ATG.js";
6
6
  import {
7
7
  createCachedOpenclawWakeResolver,
8
- postOpenclawWake
9
- } from "./chunk-NE2F5CKS.js";
8
+ postOpenclawAgent
9
+ } from "./chunk-LKGKDZSO.js";
10
10
  import "./chunk-PHXO5P25.js";
11
11
  import {
12
12
  recordDelivered
@@ -500,14 +500,13 @@ function createCodexDelivery(onDelivered) {
500
500
  }
501
501
 
502
502
  // src/delivery/openclaw.ts
503
- function formatOpenclawNudge(event) {
504
- const who = event.from?.member_id ?? "someone";
505
- const body = (event.content?.body ?? "").replace(/\s+/g, " ").slice(0, 160);
506
- return `New Kojee Tandem activity in ${event.tandem_id} from ${who}: "${body}". Catch up with tandem_messages / tandem_listen and reply via tandem_send.`;
503
+ function formatOpenclawAgentPrompt(event) {
504
+ const since = Math.max(0, (event.cursor ?? 1) - 1);
505
+ return `You were mentioned in Kojee Tandem ${event.tandem_id}. Read it to see who and what \u2014 tandem_messages(tandem_id="${event.tandem_id}", since=${since}) \u2014 then REPLY IN THE FORUM now with tandem_send(tandem_id="${event.tandem_id}", body=..., reply_to="${event.id}"). Do this now; don't just read or ack.`;
507
506
  }
508
507
  function createOpenclawDelivery(deps = {}) {
509
508
  const resolveTarget = deps.resolveTarget ?? createCachedOpenclawWakeResolver();
510
- const postWake = deps.postWake ?? postOpenclawWake;
509
+ const postAgent = deps.postAgent ?? postOpenclawAgent;
511
510
  let eventLog = null;
512
511
  let streamHandle = null;
513
512
  let warnedUnconfigured = false;
@@ -586,8 +585,13 @@ function createOpenclawDelivery(deps = {}) {
586
585
  return;
587
586
  }
588
587
  warnedUnconfigured = false;
589
- const res = await postWake(r.target, { text: formatOpenclawNudge(event), mode: "now" });
590
- if (!res.ok) console.error(`[openclaw] wake POST failed: ${res.error ?? res.status}`);
588
+ const res = await postAgent(r.target, {
589
+ message: formatOpenclawAgentPrompt(event),
590
+ channel: "last",
591
+ name: "Kojee",
592
+ wakeMode: "now"
593
+ });
594
+ if (!res.ok) console.error(`[openclaw] agent wake POST failed: ${res.error ?? res.status}`);
591
595
  })().catch((err) => {
592
596
  console.error("[openclaw] wake delivery failed:", err);
593
597
  });
@@ -614,7 +618,7 @@ function selectDelivery(runtime, opts) {
614
618
  return createCodexDelivery(opts.onDelivered);
615
619
  }
616
620
  if (runtime === "openclaw") {
617
- return createOpenclawDelivery();
621
+ return opts.headless ? createOpenclawDelivery() : null;
618
622
  }
619
623
  if (!needsWebhookEventStream(opts.env)) {
620
624
  return null;
@@ -3,15 +3,15 @@ import {
3
3
  pairSlotFor,
4
4
  reconcileConnectPairSlot,
5
5
  runWizard
6
- } from "./chunk-RTYRJAJO.js";
7
- import "./chunk-T6QANUS2.js";
6
+ } from "./chunk-ZUF47CPP.js";
7
+ import "./chunk-376ZI3J7.js";
8
8
  import "./chunk-E6WMFMM2.js";
9
9
  import "./chunk-GEGUWQYT.js";
10
10
  import {
11
11
  isWizardRuntime
12
12
  } from "./chunk-77HWBSRH.js";
13
- import "./chunk-TMCNB4JH.js";
14
13
  import "./chunk-D6JKFJ6A.js";
14
+ import "./chunk-COM6435C.js";
15
15
  import "./chunk-EIH2LNF4.js";
16
16
  import "./chunk-RDLF4NQC.js";
17
17
  import "./chunk-SQL56SEB.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.7.5-staging.7",
3
+ "version": "0.7.5-staging.8",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -1,33 +0,0 @@
1
- // src/hooks/hook-command.ts
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
- function npxHookCommand(type) {
6
- return `npx -y kojee-mcp hook --type=${type}`;
7
- }
8
- function resolveCliEntry(moduleUrl = import.meta.url) {
9
- try {
10
- const here = fileURLToPath(moduleUrl);
11
- const candidate = path.join(path.dirname(here), "cli.js");
12
- return fs.existsSync(candidate) ? candidate : null;
13
- } catch {
14
- return null;
15
- }
16
- }
17
- function buildHookCommand(type, opts = {}) {
18
- const cliEntry = opts.cliEntry !== void 0 ? opts.cliEntry : resolveCliEntry();
19
- if (!cliEntry) return npxHookCommand(type);
20
- const execPath = opts.execPath ?? process.execPath;
21
- return `"${execPath}" "${cliEntry}" hook --type=${type}`;
22
- }
23
- var PINNED_HOOK_RE = /^"[^"]+" "[^"]+" hook --type=(stop|user-prompt-submit|codex-stop|codex-prompt-submit)$/;
24
- function isKojeeHookCommand(command) {
25
- if (command.startsWith("npx -y kojee-mcp hook --type=")) return true;
26
- return PINNED_HOOK_RE.test(command);
27
- }
28
-
29
- export {
30
- npxHookCommand,
31
- buildHookCommand,
32
- isKojeeHookCommand
33
- };