kojee-mcp 0.5.14 → 0.5.15

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,9 +1,5 @@
1
1
  // src/wizard/runtimes.ts
2
2
  var WIZARD_RUNTIMES = ["claude-code", "hermes", "openclaw", "codex"];
3
- var WEBHOOK_RUNTIMES = /* @__PURE__ */ new Set([
4
- "hermes",
5
- "openclaw"
6
- ]);
7
3
  function isWizardRuntime(value) {
8
4
  return WIZARD_RUNTIMES.includes(value);
9
5
  }
@@ -16,7 +12,6 @@ var RUNTIME_MENU = [
16
12
 
17
13
  export {
18
14
  WIZARD_RUNTIMES,
19
- WEBHOOK_RUNTIMES,
20
15
  isWizardRuntime,
21
16
  RUNTIME_MENU
22
17
  };
package/dist/cli.js CHANGED
@@ -140,7 +140,7 @@ program.command("init").description(
140
140
  console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init` \u2014 or pass --token/--pair-code, or run `init` in a terminal for the guided wizard.");
141
141
  process.exit(1);
142
142
  }
143
- const { runWizard } = await import("./wizard-5ILBK6YD.js");
143
+ const { runWizard } = await import("./wizard-3FDEWEYO.js");
144
144
  const result = await runWizard({
145
145
  ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
146
146
  ...opts.uninstall ? { uninstall: true } : {},
@@ -178,7 +178,7 @@ async function withReadline(fn) {
178
178
  }
179
179
  }
180
180
  async function promptRuntimeFromTty() {
181
- const { RUNTIME_MENU } = await import("./runtimes-CO43XUUK.js");
181
+ const { RUNTIME_MENU } = await import("./runtimes-GG7EOFZH.js");
182
182
  return withReadline(async (ask) => {
183
183
  const menu = RUNTIME_MENU.map((m) => `[${m.index}] ${m.runtime}`).join(" ");
184
184
  const answer = (await ask(`Which runtime is this proxy for? ${menu}
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  RUNTIME_MENU,
3
- WEBHOOK_RUNTIMES,
4
3
  WIZARD_RUNTIMES,
5
4
  isWizardRuntime
6
- } from "./chunk-LVL25VLO.js";
5
+ } from "./chunk-77HWBSRH.js";
7
6
  export {
8
7
  RUNTIME_MENU,
9
- WEBHOOK_RUNTIMES,
10
8
  WIZARD_RUNTIMES,
11
9
  isWizardRuntime
12
10
  };
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  WIZARD_RUNTIMES,
17
17
  isWizardRuntime
18
- } from "./chunk-LVL25VLO.js";
18
+ } from "./chunk-77HWBSRH.js";
19
19
  import {
20
20
  resolveSignatureEmission,
21
21
  resolveWebhookConfig
@@ -30,8 +30,8 @@ import {
30
30
 
31
31
  // src/wizard/wizard.ts
32
32
  import crypto2 from "crypto";
33
- import fs4 from "fs";
34
- import path5 from "path";
33
+ import fs5 from "fs";
34
+ import path6 from "path";
35
35
  import { fileURLToPath } from "url";
36
36
 
37
37
  // src/wizard/registry.ts
@@ -318,6 +318,141 @@ function installHermes(inp) {
318
318
  };
319
319
  }
320
320
 
321
+ // src/wizard/capabilities/openclaw-channel-config.ts
322
+ import fs4 from "fs";
323
+ import path5 from "path";
324
+ var CHANNEL_ID = "kojee-tandem";
325
+ function mergeOpenclawChannelConfig(existing, block) {
326
+ const prevChannels = existing.channels && typeof existing.channels === "object" ? existing.channels : {};
327
+ return {
328
+ ...existing,
329
+ channels: {
330
+ ...prevChannels,
331
+ [CHANNEL_ID]: { ...block }
332
+ }
333
+ };
334
+ }
335
+ function readOpenclawConfigState(configPath) {
336
+ let raw;
337
+ try {
338
+ raw = fs4.readFileSync(configPath, "utf8");
339
+ } catch {
340
+ return { cfg: {}, unparseable: false };
341
+ }
342
+ try {
343
+ const parsed = JSON.parse(raw);
344
+ return {
345
+ cfg: parsed && typeof parsed === "object" ? parsed : {},
346
+ unparseable: false
347
+ };
348
+ } catch {
349
+ return { cfg: {}, unparseable: true };
350
+ }
351
+ }
352
+ function atomicWrite(filePath, content, secret) {
353
+ fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
354
+ const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
355
+ fs4.writeFileSync(tmp, content, { ...secret ? { mode: 384 } : {} });
356
+ if (secret) secureFile(tmp);
357
+ fs4.renameSync(tmp, filePath);
358
+ if (secret) secureFile(filePath);
359
+ }
360
+ function writeOpenclawChannelConfig(configPath, block, opts = {}) {
361
+ const { cfg, unparseable } = readOpenclawConfigState(configPath);
362
+ let backedUp;
363
+ if (unparseable) {
364
+ const stamp = opts.timestamp ?? corruptStamp();
365
+ backedUp = `${configPath}.corrupt-${stamp}`;
366
+ fs4.copyFileSync(configPath, backedUp);
367
+ }
368
+ const merged = mergeOpenclawChannelConfig(cfg, block);
369
+ atomicWrite(configPath, JSON.stringify(merged, null, 2) + "\n", Boolean(block.credential));
370
+ return backedUp ? { backedUp } : {};
371
+ }
372
+ function corruptStamp() {
373
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
374
+ }
375
+ function removeOpenclawChannel(configPath) {
376
+ let raw;
377
+ try {
378
+ raw = fs4.readFileSync(configPath, "utf8");
379
+ } catch {
380
+ return false;
381
+ }
382
+ let cfg;
383
+ try {
384
+ cfg = JSON.parse(raw);
385
+ } catch {
386
+ return false;
387
+ }
388
+ const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : void 0;
389
+ if (!channels || !(CHANNEL_ID in channels)) return false;
390
+ const { [CHANNEL_ID]: _removed, ...rest } = channels;
391
+ const next = { ...cfg, channels: rest };
392
+ atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n", false);
393
+ return true;
394
+ }
395
+
396
+ // src/wizard/installers/openclaw.ts
397
+ function installOpenclaw(inp) {
398
+ const gatewayUrl = inp.url ? inp.url.replace(/\/+$/, "") : void 0;
399
+ const block = {
400
+ enabled: true,
401
+ ...gatewayUrl ? { gatewayUrl } : {},
402
+ // Token-mode → credential lands here (config-first). Paired-mode → omitted.
403
+ ...inp.token ? { credential: inp.token } : {},
404
+ tandems: [],
405
+ selfPrincipals: [],
406
+ wake: { severities: [], mentionsOnly: false }
407
+ };
408
+ const stamp = (inp.now ? inp.now() : /* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
409
+ const { backedUp } = writeOpenclawChannelConfig(inp.openclawConfigPath, block, { timestamp: stamp });
410
+ const installCmd = inp.pluginSourceDir ? `openclaw plugins install -l ${inp.pluginSourceDir}` : `openclaw plugins install npm:openclaw-channel-kojee-tandem (available once published)`;
411
+ const lines = [];
412
+ if (backedUp) {
413
+ lines.push(
414
+ `WARNING: ${inp.openclawConfigPath} was present but could not be parsed as JSON.`,
415
+ ` The original was backed up to ${backedUp} before writing the kojee-tandem block.`,
416
+ ` If it held other channels, recover them from that backup and re-merge by hand.`,
417
+ ""
418
+ );
419
+ }
420
+ lines.push(
421
+ "Configured runtime: openclaw (in-process channel plugin)",
422
+ "Wake mode: native OpenClaw channel \u2014 the plugin streams Tandem events while the gateway is up.",
423
+ ` channel config: ${inp.openclawConfigPath} (channels.${CHANNEL_ID})`,
424
+ inp.token ? ` credential: written into the channel block (owner-only perms; the plugin's config-first path)` : ` credential: shared ~/.kojee/config.json (paired) \u2014 not duplicated into the channel block`,
425
+ "",
426
+ "Next steps (openclaw):",
427
+ ` - Install the plugin via OpenClaw's own plugin manager: ${installCmd}`,
428
+ " - Reload the gateway to load the plugin: openclaw gateway restart",
429
+ " - Verify: openclaw plugins inspect kojee-tandem / openclaw channels status"
430
+ );
431
+ return {
432
+ runtime: "openclaw",
433
+ output: lines.join("\n"),
434
+ exitCode: 0,
435
+ configPath: inp.openclawConfigPath
436
+ };
437
+ }
438
+ function uninstallOpenclaw(inp) {
439
+ const removed = removeOpenclawChannel(inp.openclawConfigPath);
440
+ const lines = ["Uninstalling runtime: openclaw (in-process channel plugin)"];
441
+ lines.push(
442
+ removed ? ` removed channels.${CHANNEL_ID} from ${inp.openclawConfigPath} (sibling channels preserved)` : ` no channels.${CHANNEL_ID} block found in ${inp.openclawConfigPath} \u2014 nothing to remove`
443
+ );
444
+ lines.push("");
445
+ lines.push("Next steps (openclaw):");
446
+ lines.push(" - Remove the plugin via OpenClaw's own plugin manager: openclaw plugins uninstall kojee-tandem");
447
+ lines.push(" - Reload the gateway: openclaw gateway restart");
448
+ return {
449
+ runtime: "openclaw",
450
+ output: lines.join("\n"),
451
+ exitCode: 0,
452
+ configPath: inp.openclawConfigPath
453
+ };
454
+ }
455
+
321
456
  // src/wizard/wizard.ts
322
457
  var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
323
458
  function generateWebhookSecret() {
@@ -383,22 +518,6 @@ function resolveWizardWebhook(opts) {
383
518
  ...warning !== void 0 ? { warning } : {}
384
519
  };
385
520
  }
386
- function writeRuntimeEnvFile(runtime, url, secret, signatureEnv = []) {
387
- const envPath = path5.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
388
- const body = [
389
- `# kojee daemon env for runtime=${runtime} (source this before starting the daemon)`,
390
- `export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`,
391
- `export KOJEE_WEBHOOK_URL=${shellSingleQuote(url)}`,
392
- `export KOJEE_WEBHOOK_SECRET=${shellSingleQuote(secret)}`,
393
- // Signature emission overrides (0.5.3) — only when explicitly configured.
394
- ...signatureEnv.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`),
395
- ""
396
- ].join("\n");
397
- fs4.mkdirSync(path5.dirname(envPath), { recursive: true, mode: 448 });
398
- fs4.writeFileSync(envPath, body, { mode: 384 });
399
- secureFile(envPath);
400
- return envPath;
401
- }
402
521
  var CODEX_UNVERIFIED_NOTE = "NOTE: live Codex verification (hook fires, MCP server connects, bounded listen works) has not been run on this build \u2014 confirm in a real Codex session. This is the owner morning step.";
403
522
  async function gatherGuidedInputs(runtime, opts) {
404
523
  const preamble = [];
@@ -599,26 +718,8 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
599
718
  lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
600
719
  return lines;
601
720
  }
602
- function configureWebhookDaemon(runtime, opts) {
603
- const wh = resolveWizardWebhook(opts);
604
- if (wh.error) {
605
- return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
606
- }
607
- recordRuntime(runtime);
608
- const lines = [];
609
- lines.push(`Configured runtime: ${runtime}`);
610
- lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
611
- lines.push("");
612
- if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
613
- const envFile = wh.url ? writeRuntimeEnvFile(runtime, wh.url, wh.secret, wh.signatureEnv) : void 0;
614
- lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
615
- lines.push("");
616
- lines.push(`Next steps (${runtime}):`);
617
- lines.push(" Start the daemon with the env above. Verify: kojee-mcp doctor (after the daemon is up).");
618
- return { runtime, output: lines.join("\n"), exitCode: 0 };
619
- }
620
721
  function distDir() {
621
- return path5.dirname(fileURLToPath(import.meta.url));
722
+ return path6.dirname(fileURLToPath(import.meta.url));
622
723
  }
623
724
  function resolveBinPath() {
624
725
  const entry = process.argv[1];
@@ -663,7 +764,7 @@ function configureHermes(opts) {
663
764
  return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
664
765
  }
665
766
  recordRuntime(runtime);
666
- const envFile = path5.join(home, ".kojee", "hermes.env");
767
+ const envFile = path6.join(home, ".kojee", "hermes.env");
667
768
  lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
668
769
  lines.push("");
669
770
  lines.push(install.output);
@@ -678,6 +779,35 @@ function configureHermes(opts) {
678
779
  lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
679
780
  return { runtime, output: lines.join("\n"), exitCode: 0 };
680
781
  }
782
+ function openclawConfigPath(opts) {
783
+ return opts.openclawConfigPath ?? path6.join(kojeeHomeDir(), ".openclaw", "config.json");
784
+ }
785
+ function resolveOpenclawPluginSourceDir() {
786
+ const candidates = [
787
+ path6.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
788
+ path6.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
789
+ ];
790
+ for (const dir of candidates) {
791
+ if (fs5.existsSync(path6.join(dir, "openclaw.plugin.json"))) return dir;
792
+ }
793
+ return void 0;
794
+ }
795
+ function configureOpenclaw(opts) {
796
+ const runtime = "openclaw";
797
+ const env = opts.env ?? process.env;
798
+ const token = (opts.token ?? "").trim();
799
+ const url = (opts.url ?? env["KOJEE_GATEWAY_URL"] ?? "").trim();
800
+ const pluginSourceDir = resolveOpenclawPluginSourceDir();
801
+ const install = installOpenclaw({
802
+ homeDir: kojeeHomeDir(),
803
+ openclawConfigPath: openclawConfigPath(opts),
804
+ ...url ? { url } : {},
805
+ ...token ? { token } : {},
806
+ ...pluginSourceDir ? { pluginSourceDir } : {}
807
+ });
808
+ recordRuntime(runtime);
809
+ return { runtime, output: install.output, exitCode: install.exitCode };
810
+ }
681
811
  async function runWizardUninstall(runtime, opts) {
682
812
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
683
813
  const lines = [`Uninstalling runtime: ${effective}`];
@@ -705,12 +835,15 @@ async function runWizardUninstall(runtime, opts) {
705
835
  });
706
836
  lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
707
837
  lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
838
+ } else if (effective === "openclaw") {
839
+ const un = uninstallOpenclaw({ openclawConfigPath: openclawConfigPath(opts) });
840
+ lines.push(un.output);
708
841
  } else {
709
- lines.push(" (hermes/openclaw write no MCP-config or hooks \u2014 nothing to tear down.");
842
+ lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
710
843
  lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
711
- const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
844
+ const envPath = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
712
845
  try {
713
- fs4.unlinkSync(envPath);
846
+ fs5.unlinkSync(envPath);
714
847
  lines.push(` removed ${envPath}`);
715
848
  } catch {
716
849
  }
@@ -758,8 +891,8 @@ registerBuiltinInstaller(
758
891
  registerBuiltinInstaller(
759
892
  "openclaw",
760
893
  "OpenClaw",
761
- ["pair-credential", "webhook"],
762
- (o) => configureWebhookDaemon("openclaw", o)
894
+ ["pair-credential", "in-process-plugin"],
895
+ (o) => configureOpenclaw(o)
763
896
  );
764
897
  export {
765
898
  CODEX_UNVERIFIED_NOTE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {