kojee-mcp 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/chunk-5DHIUN73.js +25 -0
  2. package/dist/{chunk-OB5T6P24.js → chunk-E35VWFZV.js} +1 -1
  3. package/dist/{chunk-GVWYW7MY.js → chunk-EDHG4375.js} +241 -58
  4. package/dist/{chunk-FSKGQ6GT.js → chunk-HI42GBQ3.js} +5 -0
  5. package/dist/chunk-IZN7IZPW.js +132 -0
  6. package/dist/{chunk-5H75AEZ2.js → chunk-R5GC2GRD.js} +62 -11
  7. package/dist/{chunk-OBIJ6WQP.js → chunk-RVLUZLXD.js} +63 -22
  8. package/dist/{chunk-QJFMU4QC.js → chunk-TMCNB4JH.js} +1 -1
  9. package/dist/{chunk-QEJUNP3X.js → chunk-XFGGMDZ4.js} +6 -5
  10. package/dist/chunk-XLRF5ATG.js +103 -0
  11. package/dist/{chunk-SGRVG4HW.js → chunk-ZUIYFRO5.js} +62 -30
  12. package/dist/cli.js +24 -14
  13. package/dist/codex-prompt-submit-hook-FOBPGZHJ.js +39 -0
  14. package/dist/codex-stop-hook-PNWYNQKM.js +136 -0
  15. package/dist/{connect-handler-HIRM624N.js → connect-handler-2JFIMQY6.js} +15 -7
  16. package/dist/{doctor-PLKLHTJM.js → doctor-HXMCO5PR.js} +2 -2
  17. package/dist/doctor-codex-ZSALZPH3.js +370 -0
  18. package/dist/{event-stream-KRYWEYWO.js → event-stream-WPN3EN7C.js} +5 -1
  19. package/dist/index.js +6 -5
  20. package/dist/{install-V7LSQCYZ.js → install-GGI6GU6C.js} +1 -1
  21. package/dist/lib.d.ts +40 -15
  22. package/dist/lib.js +7 -7
  23. package/dist/pending-state-6TVRR63P.js +134 -0
  24. package/dist/{registry-VRFHKOPP.js → registry-42Y45L6I.js} +114 -47
  25. package/dist/{server-YTHOMA4H.js → server-JWIH7OFA.js} +5 -2
  26. package/dist/{setup-handler-UHVKSX7E.js → setup-handler-GBPXDETR.js} +8 -7
  27. package/dist/{stop-hook-YI4KRKF2.js → stop-hook-PWTAP227.js} +2 -2
  28. package/dist/{tail-stream-NBGHHBS4.js → tail-stream-N43D53RC.js} +99 -19
  29. package/package.json +3 -2
  30. package/skills/using-tandems/SKILL.md +52 -0
  31. package/dist/chunk-WIU5WGDF.js +0 -10
  32. package/dist/codex-stop-hook-EOMQD3J4.js +0 -69
  33. package/dist/doctor-codex-HARAAH4Y.js +0 -154
@@ -0,0 +1,25 @@
1
+ // src/version.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ var FALLBACK_VERSION = "0.0.0-unknown";
6
+ function resolveVersion() {
7
+ try {
8
+ const here = path.dirname(fileURLToPath(import.meta.url));
9
+ const parsed = JSON.parse(
10
+ fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
11
+ );
12
+ return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
13
+ } catch (err) {
14
+ process.stderr.write(
15
+ `kojee-mcp: could not resolve version from package.json, falling back to ${FALLBACK_VERSION}: ${String(err)}
16
+ `
17
+ );
18
+ return FALLBACK_VERSION;
19
+ }
20
+ }
21
+ var VERSION = resolveVersion();
22
+
23
+ export {
24
+ VERSION
25
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildReplyRecipe
3
- } from "./chunk-QEJUNP3X.js";
3
+ } from "./chunk-XFGGMDZ4.js";
4
4
 
5
5
  // src/adapters/claude-code.ts
6
6
  function computeSeverity(event) {
@@ -1,13 +1,19 @@
1
- import {
2
- removeOpenclawMcpServer,
3
- writeOpenclawMcpConfig
4
- } from "./chunk-E6WMFMM2.js";
5
1
  import {
6
2
  buildCodexMcpServerTable,
3
+ buildCodexPromptSubmitHookBlock,
7
4
  buildCodexStopHookBlock,
5
+ isPlaceholderWebhookUrl,
8
6
  removeCodexConfig,
9
7
  writeCodexConfig
10
- } from "./chunk-OBIJ6WQP.js";
8
+ } from "./chunk-RVLUZLXD.js";
9
+ import {
10
+ removeOpenclawMcpServer,
11
+ writeOpenclawMcpConfig
12
+ } from "./chunk-E6WMFMM2.js";
13
+ import {
14
+ WIZARD_RUNTIMES,
15
+ isWizardRuntime
16
+ } from "./chunk-77HWBSRH.js";
11
17
  import {
12
18
  CONNECT_RUNTIMES
13
19
  } from "./chunk-EIH2LNF4.js";
@@ -19,10 +25,6 @@ import {
19
25
  import {
20
26
  kojeeHomeDir
21
27
  } from "./chunk-SQL56SEB.js";
22
- import {
23
- WIZARD_RUNTIMES,
24
- isWizardRuntime
25
- } from "./chunk-77HWBSRH.js";
26
28
  import {
27
29
  resolveSignatureEmission,
28
30
  resolveWebhookConfig
@@ -40,10 +42,14 @@ import {
40
42
  secureFile,
41
43
  unlinkSyncRetry
42
44
  } from "./chunk-U5HHHRXA.js";
45
+ import {
46
+ VERSION
47
+ } from "./chunk-5DHIUN73.js";
43
48
  import {
44
49
  CODEX_LISTEN_CAP_MS,
50
+ buildCondensedTandemRules,
45
51
  buildWebhookReceiverNote
46
- } from "./chunk-QEJUNP3X.js";
52
+ } from "./chunk-XFGGMDZ4.js";
47
53
 
48
54
  // src/wizard/pair-slot.ts
49
55
  import fs from "fs";
@@ -128,10 +134,161 @@ function pairSlotFor(runtime, code) {
128
134
  }
129
135
 
130
136
  // src/wizard/wizard.ts
131
- import crypto2 from "crypto";
132
- import fs5 from "fs";
133
- import path6 from "path";
137
+ import crypto3 from "crypto";
138
+ import fs6 from "fs";
139
+ import path7 from "path";
140
+ import { fileURLToPath as fileURLToPath2 } from "url";
141
+
142
+ // src/wizard/skill-install.ts
143
+ import crypto from "crypto";
144
+ import fs2 from "fs";
145
+ import path2 from "path";
134
146
  import { fileURLToPath } from "url";
147
+ var SKILL_NAME = "using-tandems";
148
+ var SKILL_MARKER_PREFIX = "kojee-mcp:skill using-tandems";
149
+ function hashSkillBody(body) {
150
+ return crypto.createHash("sha256").update(body, "utf8").digest("hex").slice(0, 12);
151
+ }
152
+ function bundledSkillSourcePath() {
153
+ const here = path2.dirname(fileURLToPath(import.meta.url));
154
+ const candidates = [
155
+ path2.join(here, "..", "skills", SKILL_NAME, "SKILL.md"),
156
+ path2.join(here, "..", "..", "skills", SKILL_NAME, "SKILL.md")
157
+ ];
158
+ for (const c of candidates) {
159
+ if (fs2.existsSync(c)) return c;
160
+ }
161
+ throw new Error(
162
+ `bundled skill '${SKILL_NAME}' not found (looked at: ${candidates.join(", ")}) \u2014 the package is missing its skills/ payload; reinstall kojee-mcp.`
163
+ );
164
+ }
165
+ function readBundledSkill() {
166
+ const body = fs2.readFileSync(bundledSkillSourcePath(), "utf8").replace(/\s+$/, "") + "\n";
167
+ return { body, version: VERSION, hash: hashSkillBody(body) };
168
+ }
169
+ function markerLine(version, hash) {
170
+ return `<!-- ${SKILL_MARKER_PREFIX} v${version} sha256:${hash} \u2014 managed by kojee-mcp; your edits are preserved (a modified file is never auto-updated or auto-removed) -->`;
171
+ }
172
+ function renderInstalledSkill(b) {
173
+ return `${b.body}
174
+ ${markerLine(b.version, hashSkillBody(b.body))}
175
+ `;
176
+ }
177
+ var MARKER_RE = new RegExp(`<!-- ${SKILL_MARKER_PREFIX} v(\\S+) sha256:([0-9a-f]{12})[^>]*-->`);
178
+ function parseInstalledSkill(content) {
179
+ const m = MARKER_RE.exec(content);
180
+ if (!m) return null;
181
+ const before = content.slice(0, m.index);
182
+ const after = content.slice(m.index + m[0].length);
183
+ const body = (before + after).replace(/\s+$/, "") + "\n";
184
+ return { version: m[1], hash: m[2], body };
185
+ }
186
+ function compareVersions(a, b) {
187
+ const parse = (v) => (v.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
188
+ const x = parse(a);
189
+ const y = parse(b);
190
+ for (let i = 0; i < 3; i++) {
191
+ const d = (x[i] ?? 0) - (y[i] ?? 0);
192
+ if (d !== 0) return d < 0 ? -1 : 1;
193
+ }
194
+ return 0;
195
+ }
196
+ function readDest(dest) {
197
+ try {
198
+ return { existing: fs2.readFileSync(dest, "utf8"), unreadable: false };
199
+ } catch (err) {
200
+ if (err.code === "ENOENT") return { existing: null, unreadable: false };
201
+ return { existing: null, unreadable: true };
202
+ }
203
+ }
204
+ function installSkillFile(skillsRoot, bundled = readBundledSkill()) {
205
+ const dir = path2.join(skillsRoot, SKILL_NAME);
206
+ const dest = path2.join(dir, "SKILL.md");
207
+ const { existing, unreadable } = readDest(dest);
208
+ if (unreadable) return { status: "skipped-unreadable", path: dest };
209
+ if (existing === null) {
210
+ fs2.mkdirSync(dir, { recursive: true });
211
+ fs2.writeFileSync(dest, renderInstalledSkill(bundled));
212
+ return { status: "installed", path: dest };
213
+ }
214
+ const parsed = parseInstalledSkill(existing);
215
+ if (!parsed) return { status: "skipped-unmanaged", path: dest };
216
+ if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
217
+ const cmp = compareVersions(bundled.version, parsed.version);
218
+ if (cmp < 0) return { status: "kept-newer", path: dest };
219
+ if (cmp === 0 && hashSkillBody(bundled.body) === parsed.hash)
220
+ return { status: "up-to-date", path: dest };
221
+ fs2.writeFileSync(dest, renderInstalledSkill(bundled));
222
+ return { status: "updated", path: dest };
223
+ }
224
+ function removeSkillFile(skillsRoot) {
225
+ const dir = path2.join(skillsRoot, SKILL_NAME);
226
+ const dest = path2.join(dir, "SKILL.md");
227
+ const { existing, unreadable } = readDest(dest);
228
+ if (unreadable) return { status: "skipped-unreadable", path: dest };
229
+ if (existing === null) return { status: "not-found", path: dest };
230
+ const parsed = parseInstalledSkill(existing);
231
+ if (!parsed) return { status: "skipped-unmanaged", path: dest };
232
+ if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
233
+ fs2.unlinkSync(dest);
234
+ try {
235
+ fs2.rmdirSync(dir);
236
+ } catch {
237
+ }
238
+ return { status: "removed", path: dest };
239
+ }
240
+ function skillsRootFor(runtime, homeDir = kojeeHomeDir()) {
241
+ if (runtime === "claude-code") return path2.join(homeDir, ".claude", "skills");
242
+ if (runtime === "codex") return path2.join(homeDir, ".agents", "skills");
243
+ return null;
244
+ }
245
+ var LABEL = `skill (${SKILL_NAME})`;
246
+ function installSkillForRuntime(runtime, skillsRootOverride) {
247
+ const root = skillsRootOverride ?? skillsRootFor(runtime);
248
+ if (!root) return null;
249
+ try {
250
+ const r = installSkillFile(root);
251
+ switch (r.status) {
252
+ case "installed":
253
+ return `${LABEL}: \u2713 installed \u2192 ${r.path}`;
254
+ case "updated":
255
+ return `${LABEL}: \u21BB updated to v${VERSION} \u2192 ${r.path}`;
256
+ case "up-to-date":
257
+ return `${LABEL}: \u21BB up to date (v${VERSION})`;
258
+ case "kept-newer":
259
+ return `${LABEL}: \u2014 kept (installed copy is newer than this proxy's v${VERSION})`;
260
+ case "skipped-unmanaged":
261
+ return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists without a kojee-mcp marker (not ours; left untouched)`;
262
+ case "skipped-modified":
263
+ return `${LABEL}: \u26A0 skipped \u2014 ${r.path} was modified locally (edits preserved; delete the file to receive updates)`;
264
+ case "skipped-unreadable":
265
+ return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists but could not be read (not verifying ownership \u2192 not touching it)`;
266
+ }
267
+ } catch (err) {
268
+ return `${LABEL}: WARNING \u2014 could not install (${err.message}); the runtime still works without it.`;
269
+ }
270
+ }
271
+ function removeSkillForRuntime(runtime, skillsRootOverride) {
272
+ const root = skillsRootOverride ?? skillsRootFor(runtime);
273
+ if (!root) return null;
274
+ try {
275
+ const r = removeSkillFile(root);
276
+ switch (r.status) {
277
+ case "removed":
278
+ return `${LABEL}: \u2713 removed ${r.path}`;
279
+ case "not-found":
280
+ return `${LABEL}: \u2014 not found`;
281
+ case "skipped-unmanaged":
282
+ return `${LABEL}: \u26A0 left in place \u2014 ${r.path} has no kojee-mcp marker (not ours)`;
283
+ case "skipped-modified":
284
+ return `${LABEL}: \u26A0 left in place \u2014 ${r.path} was modified locally (never deleting your edits; remove it manually)`;
285
+ case "skipped-unreadable":
286
+ return `${LABEL}: \u26A0 left in place \u2014 ${r.path} exists but could not be read (cannot verify it's ours)`;
287
+ }
288
+ } catch (err) {
289
+ return `${LABEL}: WARNING \u2014 could not remove (${err.message}).`;
290
+ }
291
+ }
135
292
 
136
293
  // src/wizard/registry.ts
137
294
  var installers = /* @__PURE__ */ new Map();
@@ -146,16 +303,16 @@ function runtimeUsesWebhook(id) {
146
303
  }
147
304
 
148
305
  // src/wizard/capabilities/webhook-secret.ts
149
- import crypto from "crypto";
150
- import fs2 from "fs";
151
- import path2 from "path";
306
+ import crypto2 from "crypto";
307
+ import fs3 from "fs";
308
+ import path3 from "path";
152
309
  function generateSecret() {
153
- return crypto.randomBytes(32).toString("hex");
310
+ return crypto2.randomBytes(32).toString("hex");
154
311
  }
155
312
  function readSecretFromEnv(envPath) {
156
313
  let body;
157
314
  try {
158
- body = fs2.readFileSync(envPath, "utf8");
315
+ body = fs3.readFileSync(envPath, "utf8");
159
316
  } catch {
160
317
  return void 0;
161
318
  }
@@ -168,10 +325,10 @@ function shellSingleQuote(value) {
168
325
  return `'${value.replace(/'/g, `'\\''`)}'`;
169
326
  }
170
327
  function upsertEnvFile(filePath, vars, opts = {}) {
171
- fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
328
+ fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
172
329
  let lines = [];
173
330
  try {
174
- lines = fs2.readFileSync(filePath, "utf8").split("\n");
331
+ lines = fs3.readFileSync(filePath, "utf8").split("\n");
175
332
  } catch {
176
333
  }
177
334
  if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
@@ -183,7 +340,7 @@ function upsertEnvFile(filePath, vars, opts = {}) {
183
340
  if (idx >= 0) lines[idx] = line;
184
341
  else lines.push(line);
185
342
  }
186
- fs2.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
343
+ fs3.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
187
344
  }
188
345
  function writeWebhookSecretBothSides(opts) {
189
346
  const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
@@ -211,11 +368,11 @@ function writeWebhookSecretBothSides(opts) {
211
368
  }
212
369
 
213
370
  // src/wizard/installers/hermes.ts
214
- import path5 from "path";
371
+ import path6 from "path";
215
372
 
216
373
  // src/wizard/plugin-payload.ts
217
- import fs3 from "fs";
218
- import path3 from "path";
374
+ import fs4 from "fs";
375
+ import path4 from "path";
219
376
  var HERMES_PAYLOAD_FILES = [
220
377
  "__init__.py",
221
378
  "adapter.py",
@@ -223,24 +380,24 @@ var HERMES_PAYLOAD_FILES = [
223
380
  "plugin.yaml"
224
381
  ];
225
382
  function stagePluginPayload(opts) {
226
- fs3.mkdirSync(opts.destDir, { recursive: true });
383
+ fs4.mkdirSync(opts.destDir, { recursive: true });
227
384
  const written = [];
228
385
  for (const file of opts.files) {
229
- const src = path3.join(opts.srcDir, file);
230
- if (!fs3.existsSync(src)) {
386
+ const src = path4.join(opts.srcDir, file);
387
+ if (!fs4.existsSync(src)) {
231
388
  throw new Error(
232
389
  `stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
233
390
  );
234
391
  }
235
- const dest = path3.join(opts.destDir, file);
236
- fs3.copyFileSync(src, dest);
392
+ const dest = path4.join(opts.destDir, file);
393
+ fs4.copyFileSync(src, dest);
237
394
  written.push(dest);
238
395
  }
239
396
  return written;
240
397
  }
241
398
  function resolveBundledPayloadDir(runtime, baseDir) {
242
- const dir = path3.join(baseDir, "plugins", runtime);
243
- if (!fs3.existsSync(dir) || !fs3.statSync(dir).isDirectory()) {
399
+ const dir = path4.join(baseDir, "plugins", runtime);
400
+ if (!fs4.existsSync(dir) || !fs4.statSync(dir).isDirectory()) {
244
401
  throw new Error(
245
402
  `resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
246
403
  );
@@ -248,10 +405,10 @@ function resolveBundledPayloadDir(runtime, baseDir) {
248
405
  return dir;
249
406
  }
250
407
  function missingPayloadFiles(runtime, baseDir, files) {
251
- const dir = path3.join(baseDir, "plugins", runtime);
252
- const dirOk = fs3.existsSync(dir) && fs3.statSync(dir).isDirectory();
408
+ const dir = path4.join(baseDir, "plugins", runtime);
409
+ const dirOk = fs4.existsSync(dir) && fs4.statSync(dir).isDirectory();
253
410
  if (!dirOk) return [...files];
254
- return files.filter((f) => !fs3.existsSync(path3.join(dir, f)));
411
+ return files.filter((f) => !fs4.existsSync(path4.join(dir, f)));
255
412
  }
256
413
  function installBundledPayload(opts) {
257
414
  const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
@@ -259,8 +416,8 @@ function installBundledPayload(opts) {
259
416
  }
260
417
 
261
418
  // src/wizard/service.ts
262
- import fs4 from "fs";
263
- import path4 from "path";
419
+ import fs5 from "fs";
420
+ import path5 from "path";
264
421
  function systemdUnit(spec) {
265
422
  return [
266
423
  "[Unit]",
@@ -307,7 +464,7 @@ function launchdPlist(spec, label) {
307
464
  }
308
465
  function planService(platform, spec) {
309
466
  if (platform === "linux") {
310
- const unitPath = path4.join(
467
+ const unitPath = path5.join(
311
468
  spec.homeDir,
312
469
  ".config",
313
470
  "systemd",
@@ -324,7 +481,7 @@ function planService(platform, spec) {
324
481
  }
325
482
  if (platform === "darwin") {
326
483
  const label = darwinLabel(spec.serviceName);
327
- const unitPath = path4.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
484
+ const unitPath = path5.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
328
485
  return {
329
486
  supported: true,
330
487
  unitPath,
@@ -344,23 +501,23 @@ function planService(platform, spec) {
344
501
  function writeService(platform, spec) {
345
502
  const plan = planService(platform, spec);
346
503
  if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
347
- fs4.mkdirSync(path4.dirname(plan.unitPath), { recursive: true });
348
- fs4.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
504
+ fs5.mkdirSync(path5.dirname(plan.unitPath), { recursive: true });
505
+ fs5.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
349
506
  }
350
507
  return plan;
351
508
  }
352
509
 
353
510
  // src/wizard/installers/hermes.ts
354
511
  function installHermes(inp) {
355
- const daemonEnv = path5.join(inp.homeDir, ".kojee", "hermes.env");
356
- const adapterEnv = path5.join(inp.homeDir, ".hermes", ".env");
357
- const pluginDir = path5.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
512
+ const daemonEnv = path6.join(inp.homeDir, ".kojee", "hermes.env");
513
+ const adapterEnv = path6.join(inp.homeDir, ".hermes", ".env");
514
+ const pluginDir = path6.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
358
515
  if (!inp.skipPayload) {
359
516
  const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
360
517
  if (missing.length > 0) {
361
518
  return {
362
519
  runtime: "hermes",
363
- output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${path5.join(inp.payloadBaseDir, "plugins", "hermes")}. No changes were written. Run \`npm run build\` to stage dist/plugins/hermes, then retry.`,
520
+ output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${path6.join(inp.payloadBaseDir, "plugins", "hermes")}. No changes were written. Run \`npm run build\` to stage dist/plugins/hermes, then retry.`,
364
521
  exitCode: 2,
365
522
  secret: "",
366
523
  secretReused: false
@@ -442,7 +599,15 @@ function installOpenclaw(inp) {
442
599
  " - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
443
600
  " wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
444
601
  " - Restart the OpenClaw gateway to spawn the kojee MCP server: openclaw gateway restart",
445
- " - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor"
602
+ " - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
603
+ "",
604
+ // Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
605
+ // `instructions` field (buildNonChannelInstructions), but not every MCP host
606
+ // surfaces server instructions to the agent — print them here too so the
607
+ // operator can paste them into the agent's own instructions if needed.
608
+ "Agent ground rules (also delivered via the kojee MCP server's instructions;",
609
+ "add to your OpenClaw agent's instructions if it doesn't surface those):",
610
+ ...buildCondensedTandemRules().split("\n").map((l) => ` ${l}`)
446
611
  );
447
612
  return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
448
613
  }
@@ -463,7 +628,7 @@ function uninstallOpenclaw(inp) {
463
628
  var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
464
629
  var HERMES_DEFAULT_WEBHOOK_URL = "http://127.0.0.1:8645/kojee-tandem";
465
630
  function generateWebhookSecret() {
466
- return crypto2.randomBytes(32).toString("hex");
631
+ return crypto3.randomBytes(32).toString("hex");
467
632
  }
468
633
  async function resolveRuntime(opts) {
469
634
  if (opts.runtime !== void 0) {
@@ -485,7 +650,8 @@ async function resolveRuntime(opts) {
485
650
  }
486
651
  function resolveWizardWebhook(opts) {
487
652
  const env = opts.env ?? process.env;
488
- const url = (opts.webhookUrl ?? env["KOJEE_WEBHOOK_URL"] ?? "").trim();
653
+ const envUrl = (env["KOJEE_WEBHOOK_URL"] ?? "").trim();
654
+ const url = (opts.webhookUrl ?? (isPlaceholderWebhookUrl(envUrl) ? "" : envUrl)).trim();
489
655
  let secret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
490
656
  if (url && !secret) secret = generateWebhookSecret();
491
657
  const sigFormat = (opts.webhookSignatureFormat ?? env["KOJEE_WEBHOOK_SIGNATURE_FORMAT"] ?? "").trim();
@@ -597,14 +763,18 @@ async function runWizard(opts) {
597
763
  }
598
764
  const installer = getInstaller(runtime);
599
765
  const result = installer ? await installer.install({ opts: effective }) : { runtime, output: `No installer registered for runtime '${runtime}'`, exitCode: 2 };
766
+ const skillLine = result.exitCode === 0 ? installSkillForRuntime(runtime, effective.skillsRoot) : null;
767
+ const body = skillLine ? `${result.output}
768
+
769
+ ${skillLine}` : result.output;
600
770
  if (preamble.length > 0 && result.exitCode === 0) {
601
771
  return {
602
772
  runtime,
603
- output: [...preamble, "", result.output, "", whatHappensNext(runtime, effective)].join("\n"),
773
+ output: [...preamble, "", body, "", whatHappensNext(runtime, effective)].join("\n"),
604
774
  exitCode: result.exitCode
605
775
  };
606
776
  }
607
- return { runtime, output: result.output, exitCode: result.exitCode };
777
+ return { runtime, output: body, exitCode: result.exitCode };
608
778
  }
609
779
  function whatHappensNext(runtime, opts) {
610
780
  const lines = ["What happens next:"];
@@ -620,7 +790,7 @@ function whatHappensNext(runtime, opts) {
620
790
  return lines.join("\n");
621
791
  }
622
792
  async function configureClaudeCode(opts) {
623
- const { runInit } = await import("./install-V7LSQCYZ.js");
793
+ const { runInit } = await import("./install-GGI6GU6C.js");
624
794
  const report = runInit({
625
795
  ...opts.configPath ? { configPath: opts.configPath } : {},
626
796
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
@@ -679,7 +849,7 @@ function configureCodex(opts) {
679
849
  return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
680
850
  }
681
851
  const connectPairedMode = !!opts.pairedConfigPath && !wh.url && !wh.secret;
682
- const hasRealUrl = !!wh.url;
852
+ const hasRealUrl = !!wh.url && !isPlaceholderWebhookUrl(wh.url);
683
853
  const url = connectPairedMode || !hasRealUrl ? void 0 : wh.url;
684
854
  const secret = connectPairedMode || !hasRealUrl ? void 0 : wh.secret || generateWebhookSecret();
685
855
  const tokenArgs = opts.token && opts.url ? { token: opts.token, url: opts.url } : {};
@@ -716,8 +886,13 @@ function configureCodex(opts) {
716
886
  })));
717
887
  if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
718
888
  lines.push("");
719
- lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
889
+ lines.push("Wrote the Codex Stop + UserPromptSubmit hooks (~/.codex/hooks.json; inline TOML form):");
720
890
  lines.push(indent(buildCodexStopHookBlock()));
891
+ lines.push(indent(buildCodexPromptSubmitHookBlock()));
892
+ lines.push("");
893
+ lines.push(
894
+ "IMPORTANT: Codex will re-prompt to trust hooks on its next launch \u2014 approve it, or wakes silently stop."
895
+ );
721
896
  lines.push("");
722
897
  lines.push(
723
898
  connectPairedMode ? "webhook: (unchanged \u2014 `connect` rewrites only the credential args, not the wake path)" : `webhook: ${wh.url ? wh.redactedSummary : "OPTIONAL \u2014 not configured (wake is self-contained; set KOJEE_WEBHOOK_URL only for an extra low-latency push path)"}`
@@ -760,7 +935,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
760
935
  return lines;
761
936
  }
762
937
  function distDir() {
763
- return path6.dirname(fileURLToPath(import.meta.url));
938
+ return path7.dirname(fileURLToPath2(import.meta.url));
764
939
  }
765
940
  function resolveBinPath() {
766
941
  const entry = process.argv[1];
@@ -821,10 +996,13 @@ function configureHermes(opts) {
821
996
  return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
822
997
  }
823
998
  recordRuntime(runtime);
824
- const envFile = path6.join(home, ".kojee", "hermes.env");
999
+ const envFile = path7.join(home, ".kojee", "hermes.env");
825
1000
  lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
826
1001
  lines.push("");
827
1002
  lines.push(install.output);
1003
+ lines.push("");
1004
+ lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
1005
+ lines.push(indent(buildCondensedTandemRules()));
828
1006
  return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
829
1007
  }
830
1008
  recordRuntime(runtime);
@@ -834,10 +1012,13 @@ function configureHermes(opts) {
834
1012
  lines.push(" Re-run with --webhook-url once your receiver is up to complete the install");
835
1013
  lines.push(" (writes both env files, copies the plugin, installs the daemon service).");
836
1014
  lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
1015
+ lines.push("");
1016
+ lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
1017
+ lines.push(indent(buildCondensedTandemRules()));
837
1018
  return { runtime, output: lines.join("\n"), exitCode: 0 };
838
1019
  }
839
1020
  function openclawConfigPath(opts) {
840
- return opts.openclawConfigPath ?? path6.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
1021
+ return opts.openclawConfigPath ?? path7.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
841
1022
  }
842
1023
  function configureOpenclaw(opts) {
843
1024
  const runtime = "openclaw";
@@ -852,7 +1033,7 @@ async function runWizardUninstall(runtime, opts) {
852
1033
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
853
1034
  const lines = [`Uninstalling runtime: ${effective}`];
854
1035
  if (effective === "claude-code") {
855
- const { runUninstall } = await import("./install-V7LSQCYZ.js");
1036
+ const { runUninstall } = await import("./install-GGI6GU6C.js");
856
1037
  const report = runUninstall({
857
1038
  ...opts.configPath ? { configPath: opts.configPath } : {},
858
1039
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
@@ -881,13 +1062,15 @@ async function runWizardUninstall(runtime, opts) {
881
1062
  } else {
882
1063
  lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
883
1064
  lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
884
- const envPath = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
1065
+ const envPath = path7.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
885
1066
  try {
886
- fs5.unlinkSync(envPath);
1067
+ fs6.unlinkSync(envPath);
887
1068
  lines.push(` removed ${envPath}`);
888
1069
  } catch {
889
1070
  }
890
1071
  }
1072
+ const skillLine = removeSkillForRuntime(effective, opts.skillsRoot);
1073
+ if (skillLine) lines.push(skillLine);
891
1074
  clearRuntimeRecord();
892
1075
  return { runtime: effective, output: lines.join("\n"), exitCode: 0 };
893
1076
  }
@@ -41,6 +41,9 @@ function createAdaptiveWatchdog(options = {}) {
41
41
  },
42
42
  armedThresholdMs() {
43
43
  return thresholdMs();
44
+ },
45
+ resetCadenceAnchor() {
46
+ prevHeartbeatAt = null;
44
47
  }
45
48
  };
46
49
  }
@@ -443,9 +446,11 @@ function normalizeBackendEvent(raw, sseEventType) {
443
446
  }
444
447
 
445
448
  export {
449
+ UNARMED_FALLBACK_MS,
446
450
  UNDICI_DEFAULT_BODY_TIMEOUT_MS,
447
451
  createAdaptiveWatchdog,
448
452
  createBackoffController,
453
+ LOG_HEARTBEAT_INTERVAL_MS,
449
454
  startEventStream,
450
455
  statusReason,
451
456
  serializeCursorMap,