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
@@ -1,19 +1,21 @@
1
1
  import {
2
- clearRuntimeRecord,
3
- readRecordedRuntime,
4
- recordRuntime
5
- } from "./chunk-EW72ZNQL.js";
2
+ removeOpenclawMcpServer,
3
+ writeOpenclawMcpConfig
4
+ } from "./chunk-E6WMFMM2.js";
6
5
  import {
7
6
  buildCodexMcpServerTable,
8
7
  buildCodexStopHookBlock,
9
8
  removeCodexConfig,
10
9
  writeCodexConfig
11
- } from "./chunk-65KRRDHP.js";
10
+ } from "./chunk-OBIJ6WQP.js";
11
+ import {
12
+ CONNECT_RUNTIMES
13
+ } from "./chunk-EIH2LNF4.js";
12
14
  import {
13
- CHANNEL_ID,
14
- removeOpenclawChannel,
15
- writeOpenclawChannelConfig
16
- } from "./chunk-CLKCNV2A.js";
15
+ clearRuntimeRecord,
16
+ readRecordedRuntime,
17
+ recordRuntime
18
+ } from "./chunk-RDLF4NQC.js";
17
19
  import {
18
20
  kojeeHomeDir
19
21
  } from "./chunk-SQL56SEB.js";
@@ -26,20 +28,109 @@ import {
26
28
  resolveWebhookConfig
27
29
  } from "./chunk-V5VZPYMZ.js";
28
30
  import {
29
- loadPairedConfig
30
- } from "./chunk-YH27B6SW.js";
31
+ loadPairedConfig,
32
+ pairedConfigPath,
33
+ perRuntimeConfigPath
34
+ } from "./chunk-5SZHXYPK.js";
35
+ import {
36
+ defaultPairedKeystorePath,
37
+ deriveKeystorePath
38
+ } from "./chunk-6G6YYST6.js";
31
39
  import {
32
- secureFile
33
- } from "./chunk-BLEGIR35.js";
40
+ secureFile,
41
+ unlinkSyncRetry
42
+ } from "./chunk-U5HHHRXA.js";
34
43
  import {
35
44
  CODEX_LISTEN_CAP_MS,
36
45
  buildWebhookReceiverNote
37
- } from "./chunk-Q7T3WAST.js";
46
+ } from "./chunk-QEJUNP3X.js";
47
+
48
+ // src/wizard/pair-slot.ts
49
+ import fs from "fs";
50
+ import os from "os";
51
+ import path from "path";
52
+ function absPerRuntimeConfigPath(runtime) {
53
+ const p = path.resolve(perRuntimeConfigPath(runtime));
54
+ if (process.platform === "win32" && !/^[a-zA-Z]:/.test(p)) {
55
+ return path.resolve(path.join(os.homedir(), ".kojee", "agents", runtime, "config.json"));
56
+ }
57
+ return p;
58
+ }
59
+ function reconcileConnectPairSlot(runtime, code) {
60
+ const messages = [];
61
+ for (const other of CONNECT_RUNTIMES) {
62
+ if (other === runtime) continue;
63
+ const sibling = loadPairedConfig(perRuntimeConfigPath(other));
64
+ if (sibling?.token === code) {
65
+ return {
66
+ ok: false,
67
+ output: `This pair code is already connected as ${other}; use a distinct per-agent code for ${runtime}. The dashboard mints one pair code per agent.`,
68
+ exitCode: 2
69
+ };
70
+ }
71
+ }
72
+ if (!CONNECT_RUNTIMES.includes(runtime)) {
73
+ return { ok: true, messages };
74
+ }
75
+ const legacy = loadPairedConfig(pairedConfigPath());
76
+ if (legacy && legacy.token === code) {
77
+ if (legacy.runtime === "hermes") {
78
+ return {
79
+ ok: false,
80
+ output: `This pair code backs the live hermes connection (${pairedConfigPath()}); retiring it would silently break the hermes daemon. Use a distinct per-agent code for ${runtime}. The dashboard mints one pair code per agent.`,
81
+ exitCode: 2
82
+ };
83
+ }
84
+ try {
85
+ unlinkSyncRetry(pairedConfigPath());
86
+ } catch (err) {
87
+ const e = err;
88
+ if (e.code !== "ENOENT") {
89
+ return {
90
+ ok: false,
91
+ output: `Aborted: could not retire the legacy global config at ${pairedConfigPath()} (${e.message}). Close any running kojee proxy and retry.`,
92
+ exitCode: 1
93
+ };
94
+ }
95
+ }
96
+ const legacyKeystore = defaultPairedKeystorePath();
97
+ const target = deriveKeystorePath(code);
98
+ try {
99
+ if (fs.existsSync(legacyKeystore) && !fs.existsSync(target)) {
100
+ fs.renameSync(legacyKeystore, target);
101
+ }
102
+ } catch {
103
+ }
104
+ messages.push("Migrated the legacy global slot into this per-runtime connection.");
105
+ }
106
+ return { ok: true, messages };
107
+ }
108
+ function pairSlotFor(runtime, code) {
109
+ if (CONNECT_RUNTIMES.includes(runtime)) {
110
+ const configPath = absPerRuntimeConfigPath(runtime);
111
+ return {
112
+ configPath,
113
+ keystorePath: deriveKeystorePath(code),
114
+ runtime,
115
+ allowOverwrite: true,
116
+ pairedConfigPath: configPath
117
+ };
118
+ }
119
+ return {
120
+ configPath: pairedConfigPath(),
121
+ keystorePath: defaultPairedKeystorePath(),
122
+ // F2: stamp the pairing runtime into the global PairedConfig so a later
123
+ // connect-runtime reconcile can recognize a live hermes slot and refuse to
124
+ // retire it (see reconcileConnectPairSlot).
125
+ runtime,
126
+ allowOverwrite: false
127
+ };
128
+ }
38
129
 
39
130
  // src/wizard/wizard.ts
40
131
  import crypto2 from "crypto";
41
- import fs4 from "fs";
42
- import path5 from "path";
132
+ import fs5 from "fs";
133
+ import path6 from "path";
43
134
  import { fileURLToPath } from "url";
44
135
 
45
136
  // src/wizard/registry.ts
@@ -56,15 +147,15 @@ function runtimeUsesWebhook(id) {
56
147
 
57
148
  // src/wizard/capabilities/webhook-secret.ts
58
149
  import crypto from "crypto";
59
- import fs from "fs";
60
- import path from "path";
150
+ import fs2 from "fs";
151
+ import path2 from "path";
61
152
  function generateSecret() {
62
153
  return crypto.randomBytes(32).toString("hex");
63
154
  }
64
155
  function readSecretFromEnv(envPath) {
65
156
  let body;
66
157
  try {
67
- body = fs.readFileSync(envPath, "utf8");
158
+ body = fs2.readFileSync(envPath, "utf8");
68
159
  } catch {
69
160
  return void 0;
70
161
  }
@@ -77,10 +168,10 @@ function shellSingleQuote(value) {
77
168
  return `'${value.replace(/'/g, `'\\''`)}'`;
78
169
  }
79
170
  function upsertEnvFile(filePath, vars, opts = {}) {
80
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
171
+ fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
81
172
  let lines = [];
82
173
  try {
83
- lines = fs.readFileSync(filePath, "utf8").split("\n");
174
+ lines = fs2.readFileSync(filePath, "utf8").split("\n");
84
175
  } catch {
85
176
  }
86
177
  if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
@@ -92,7 +183,7 @@ function upsertEnvFile(filePath, vars, opts = {}) {
92
183
  if (idx >= 0) lines[idx] = line;
93
184
  else lines.push(line);
94
185
  }
95
- fs.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
186
+ fs2.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
96
187
  }
97
188
  function writeWebhookSecretBothSides(opts) {
98
189
  const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
@@ -120,11 +211,11 @@ function writeWebhookSecretBothSides(opts) {
120
211
  }
121
212
 
122
213
  // src/wizard/installers/hermes.ts
123
- import path4 from "path";
214
+ import path5 from "path";
124
215
 
125
216
  // src/wizard/plugin-payload.ts
126
- import fs2 from "fs";
127
- import path2 from "path";
217
+ import fs3 from "fs";
218
+ import path3 from "path";
128
219
  var HERMES_PAYLOAD_FILES = [
129
220
  "__init__.py",
130
221
  "adapter.py",
@@ -132,24 +223,24 @@ var HERMES_PAYLOAD_FILES = [
132
223
  "plugin.yaml"
133
224
  ];
134
225
  function stagePluginPayload(opts) {
135
- fs2.mkdirSync(opts.destDir, { recursive: true });
226
+ fs3.mkdirSync(opts.destDir, { recursive: true });
136
227
  const written = [];
137
228
  for (const file of opts.files) {
138
- const src = path2.join(opts.srcDir, file);
139
- if (!fs2.existsSync(src)) {
229
+ const src = path3.join(opts.srcDir, file);
230
+ if (!fs3.existsSync(src)) {
140
231
  throw new Error(
141
232
  `stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
142
233
  );
143
234
  }
144
- const dest = path2.join(opts.destDir, file);
145
- fs2.copyFileSync(src, dest);
235
+ const dest = path3.join(opts.destDir, file);
236
+ fs3.copyFileSync(src, dest);
146
237
  written.push(dest);
147
238
  }
148
239
  return written;
149
240
  }
150
241
  function resolveBundledPayloadDir(runtime, baseDir) {
151
- const dir = path2.join(baseDir, "plugins", runtime);
152
- if (!fs2.existsSync(dir) || !fs2.statSync(dir).isDirectory()) {
242
+ const dir = path3.join(baseDir, "plugins", runtime);
243
+ if (!fs3.existsSync(dir) || !fs3.statSync(dir).isDirectory()) {
153
244
  throw new Error(
154
245
  `resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
155
246
  );
@@ -157,10 +248,10 @@ function resolveBundledPayloadDir(runtime, baseDir) {
157
248
  return dir;
158
249
  }
159
250
  function missingPayloadFiles(runtime, baseDir, files) {
160
- const dir = path2.join(baseDir, "plugins", runtime);
161
- const dirOk = fs2.existsSync(dir) && fs2.statSync(dir).isDirectory();
251
+ const dir = path3.join(baseDir, "plugins", runtime);
252
+ const dirOk = fs3.existsSync(dir) && fs3.statSync(dir).isDirectory();
162
253
  if (!dirOk) return [...files];
163
- return files.filter((f) => !fs2.existsSync(path2.join(dir, f)));
254
+ return files.filter((f) => !fs3.existsSync(path3.join(dir, f)));
164
255
  }
165
256
  function installBundledPayload(opts) {
166
257
  const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
@@ -168,8 +259,8 @@ function installBundledPayload(opts) {
168
259
  }
169
260
 
170
261
  // src/wizard/service.ts
171
- import fs3 from "fs";
172
- import path3 from "path";
262
+ import fs4 from "fs";
263
+ import path4 from "path";
173
264
  function systemdUnit(spec) {
174
265
  return [
175
266
  "[Unit]",
@@ -216,7 +307,7 @@ function launchdPlist(spec, label) {
216
307
  }
217
308
  function planService(platform, spec) {
218
309
  if (platform === "linux") {
219
- const unitPath = path3.join(
310
+ const unitPath = path4.join(
220
311
  spec.homeDir,
221
312
  ".config",
222
313
  "systemd",
@@ -233,7 +324,7 @@ function planService(platform, spec) {
233
324
  }
234
325
  if (platform === "darwin") {
235
326
  const label = darwinLabel(spec.serviceName);
236
- const unitPath = path3.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
327
+ const unitPath = path4.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
237
328
  return {
238
329
  supported: true,
239
330
  unitPath,
@@ -253,23 +344,23 @@ function planService(platform, spec) {
253
344
  function writeService(platform, spec) {
254
345
  const plan = planService(platform, spec);
255
346
  if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
256
- fs3.mkdirSync(path3.dirname(plan.unitPath), { recursive: true });
257
- fs3.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
347
+ fs4.mkdirSync(path4.dirname(plan.unitPath), { recursive: true });
348
+ fs4.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
258
349
  }
259
350
  return plan;
260
351
  }
261
352
 
262
353
  // src/wizard/installers/hermes.ts
263
354
  function installHermes(inp) {
264
- const daemonEnv = path4.join(inp.homeDir, ".kojee", "hermes.env");
265
- const adapterEnv = path4.join(inp.homeDir, ".hermes", ".env");
266
- const pluginDir = path4.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
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");
267
358
  if (!inp.skipPayload) {
268
359
  const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
269
360
  if (missing.length > 0) {
270
361
  return {
271
362
  runtime: "hermes",
272
- output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${path4.join(inp.payloadBaseDir, "plugins", "hermes")}. No changes were written. Run \`npm run build\` to stage dist/plugins/hermes, then retry.`,
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.`,
273
364
  exitCode: 2,
274
365
  secret: "",
275
366
  secretReused: false
@@ -328,66 +419,49 @@ function installHermes(inp) {
328
419
 
329
420
  // src/wizard/installers/openclaw.ts
330
421
  function installOpenclaw(inp) {
331
- const gatewayUrl = inp.url ? inp.url.replace(/\/+$/, "") : void 0;
332
- const block = {
333
- enabled: true,
334
- ...gatewayUrl ? { gatewayUrl } : {},
335
- // Token-mode → credential lands here (config-first). Paired-mode → omitted.
336
- ...inp.token ? { credential: inp.token } : {},
337
- tandems: [],
338
- selfPrincipals: [],
339
- wake: { severities: [], mentionsOnly: false }
340
- };
341
- const stamp = (inp.now ? inp.now() : /* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
342
- const { backedUp } = writeOpenclawChannelConfig(inp.openclawConfigPath, block, { timestamp: stamp });
343
- const installCmd = inp.pluginSourceDir ? `openclaw plugins install -l ${inp.pluginSourceDir}` : `openclaw plugins install npm:openclaw-channel-kojee-tandem (available once published)`;
422
+ const r = writeOpenclawMcpConfig(inp.openclawConfigPath, {
423
+ ...inp.pairedConfigPath ? { pairedConfigPath: inp.pairedConfigPath } : {},
424
+ ...inp.now ? { now: inp.now } : {}
425
+ });
344
426
  const lines = [];
345
- if (backedUp) {
427
+ if (r.backedUp) {
346
428
  lines.push(
347
- `WARNING: ${inp.openclawConfigPath} was present but could not be parsed as JSON.`,
348
- ` The original was backed up to ${backedUp} before writing the kojee-tandem block.`,
349
- ` If it held other channels, recover them from that backup and re-merge by hand.`,
429
+ `WARNING: ${inp.openclawConfigPath} was present but unparseable; backed up to ${r.backedUp} before writing.`,
350
430
  ""
351
431
  );
352
432
  }
353
433
  lines.push(
354
- "Configured runtime: openclaw (in-process channel plugin)",
355
- "Wake mode: native OpenClaw channel \u2014 the plugin streams Tandem events while the gateway is up.",
356
- ` channel config: ${inp.openclawConfigPath} (channels.${CHANNEL_ID})`,
357
- 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`,
434
+ "Configured runtime: openclaw (MCP server + /wake injector)",
435
+ "Wake mode: kojee-mcp runs as OpenClaw's MCP server; Tandem events POST to the local",
436
+ " OpenClaw gateway /hooks/wake (Bearer hooks.token) \u2192 the agent wakes.",
437
+ ` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee)`,
438
+ inp.pairedConfigPath ? ` credentials: per-runtime paired slot (${inp.pairedConfigPath})` : ` credentials: shared ~/.kojee/config.json (paired)`,
439
+ ` hooks token: ${r.hooksTokenGenerated ? "generated + hooks.enabled=true (new)" : "preserved existing"}`,
358
440
  "",
359
441
  "Next steps (openclaw):",
360
- ` - Install the plugin via OpenClaw's own plugin manager: ${installCmd}`,
361
- " - Reload the gateway to load the plugin: openclaw gateway restart",
362
- " - Verify: openclaw plugins inspect kojee-tandem / openclaw channels status"
442
+ " - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
443
+ " wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
444
+ " - 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"
363
446
  );
364
- return {
365
- runtime: "openclaw",
366
- output: lines.join("\n"),
367
- exitCode: 0,
368
- configPath: inp.openclawConfigPath
369
- };
447
+ return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
370
448
  }
371
449
  function uninstallOpenclaw(inp) {
372
- const removed = removeOpenclawChannel(inp.openclawConfigPath);
373
- const lines = ["Uninstalling runtime: openclaw (in-process channel plugin)"];
374
- lines.push(
375
- removed ? ` removed channels.${CHANNEL_ID} from ${inp.openclawConfigPath} (sibling channels preserved)` : ` no channels.${CHANNEL_ID} block found in ${inp.openclawConfigPath} \u2014 nothing to remove`
376
- );
377
- lines.push("");
378
- lines.push("Next steps (openclaw):");
379
- lines.push(" - Remove the plugin via OpenClaw's own plugin manager: openclaw plugins uninstall kojee-tandem");
380
- lines.push(" - Reload the gateway: openclaw gateway restart");
381
- return {
382
- runtime: "openclaw",
383
- output: lines.join("\n"),
384
- exitCode: 0,
385
- configPath: inp.openclawConfigPath
386
- };
450
+ const removed = removeOpenclawMcpServer(inp.openclawConfigPath);
451
+ const lines = [
452
+ "Uninstalling runtime: openclaw (MCP server + /wake injector)",
453
+ removed ? ` removed mcp.servers.kojee from ${inp.openclawConfigPath} (hooks + siblings preserved)` : ` no mcp.servers.kojee in ${inp.openclawConfigPath} \u2014 nothing to remove`,
454
+ "",
455
+ "Next steps (openclaw):",
456
+ " - Restart the OpenClaw gateway: openclaw gateway restart",
457
+ " - hooks.enabled/token were left intact (you may use hooks for other integrations)."
458
+ ];
459
+ return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
387
460
  }
388
461
 
389
462
  // src/wizard/wizard.ts
390
463
  var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
464
+ var HERMES_DEFAULT_WEBHOOK_URL = "http://127.0.0.1:8645/kojee-tandem";
391
465
  function generateWebhookSecret() {
392
466
  return crypto2.randomBytes(32).toString("hex");
393
467
  }
@@ -472,15 +546,25 @@ async function gatherGuidedInputs(runtime, opts) {
472
546
  } else {
473
547
  const code = opts.promptPairCode ? (await opts.promptPairCode()).trim() : "";
474
548
  if (!code) return { error: "No pair code entered. Re-run `kojee-mcp init` and enter a pair code, or choose token mode." };
475
- const pair = opts.runPair ?? (async (a) => {
476
- const { runPair } = await import("./pair-P4ILCMT7.js");
477
- const { pairedConfigPath } = await import("./paired-config-JTFLHMZ2.js");
478
- const { defaultPairedKeystorePath } = await import("./keystore-XLEV3FL5.js");
479
- return runPair({ code: a.code, url: a.url, keystorePath: defaultPairedKeystorePath(), configPath: pairedConfigPath() });
549
+ const reconcile = reconcileConnectPairSlot(runtime, code);
550
+ if (!reconcile.ok) return { error: reconcile.output };
551
+ for (const m of reconcile.messages) preamble.push(m);
552
+ const slot = pairSlotFor(runtime, code);
553
+ const pair = opts.runPair ?? (async (o) => {
554
+ const { runPair } = await import("./pair-HTCVSMR6.js");
555
+ return runPair(o);
480
556
  });
481
557
  try {
482
- const { message } = await pair({ code, url: brokerUrl });
558
+ const { message } = await pair({
559
+ code,
560
+ url: brokerUrl,
561
+ keystorePath: slot.keystorePath,
562
+ configPath: slot.configPath,
563
+ ...slot.runtime ? { runtime: slot.runtime } : {},
564
+ ...slot.allowOverwrite ? { allowOverwrite: true } : {}
565
+ });
483
566
  preamble.push(`Auth: pair mode \u2014 ${message}`);
567
+ if (slot.pairedConfigPath) next.pairedConfigPath = slot.pairedConfigPath;
484
568
  } catch (err) {
485
569
  return { error: `Pairing failed: ${err.message}` };
486
570
  }
@@ -527,6 +611,8 @@ function whatHappensNext(runtime, opts) {
527
611
  lines.push(" - Restart your runtime so it picks up the new kojee config.");
528
612
  if (opts.token) {
529
613
  lines.push(" - First boot enrolls this token's own keystore (~/.kojee/keypair-<hash>.json).");
614
+ } else if (opts.pairedConfigPath) {
615
+ lines.push(` - The proxy uses your paired credentials (${opts.pairedConfigPath}).`);
530
616
  } else {
531
617
  lines.push(" - The proxy uses your paired credentials (~/.kojee/config.json + keypair.json).");
532
618
  }
@@ -534,13 +620,18 @@ function whatHappensNext(runtime, opts) {
534
620
  return lines.join("\n");
535
621
  }
536
622
  async function configureClaudeCode(opts) {
537
- const { runInit } = await import("./install-LJY2CHKG.js");
623
+ const { runInit } = await import("./install-V7LSQCYZ.js");
538
624
  const report = runInit({
539
625
  ...opts.configPath ? { configPath: opts.configPath } : {},
540
626
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
541
627
  // Token mode threads --token/--url into the written args (per-token
542
628
  // keystore). Paired mode leaves both unset ⇒ args stay `["kojee-mcp"]`.
543
- ...opts.token && opts.url ? { token: opts.token, url: opts.url } : {}
629
+ ...opts.token && opts.url ? { token: opts.token, url: opts.url } : {},
630
+ // Per-runtime paired mode (connect): embed --paired-config, overwrite a
631
+ // stale entry so a re-connect can never leave a launcher reading old creds,
632
+ // and ensureCli so a fresh box with no ~/.claude.json still gets the
633
+ // launcher WRITTEN (never a silent "not-found" that drops the wrong slot).
634
+ ...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath, overwrite: true, ensureCli: true } : {}
544
635
  });
545
636
  recordRuntime("claude-code");
546
637
  return { runtime: "claude-code", output: formatClaudeInit(report), exitCode: 0 };
@@ -549,6 +640,7 @@ function formatClaudeInit(report) {
549
640
  const tick = (s) => {
550
641
  if (s === "added") return "\u2713 added";
551
642
  if (s === "already-installed") return "\u21BB already installed";
643
+ if (s === "updated") return "\u21BB updated";
552
644
  if (s === "preserved-different") return "\u26A0 preserved (existing entry differs \u2014 left untouched)";
553
645
  if (s === "not-found") return "\u2014 not found";
554
646
  return s ?? "\u2014";
@@ -586,44 +678,60 @@ function configureCodex(opts) {
586
678
  if (wh.error) {
587
679
  return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
588
680
  }
589
- const url = wh.url || "https://YOUR-CODEX-WEBHOOK-RECEIVER.local/kojee";
590
- const secret = wh.secret || generateWebhookSecret();
681
+ const connectPairedMode = !!opts.pairedConfigPath && !wh.url && !wh.secret;
682
+ const hasRealUrl = !!wh.url;
683
+ const url = connectPairedMode || !hasRealUrl ? void 0 : wh.url;
684
+ const secret = connectPairedMode || !hasRealUrl ? void 0 : wh.secret || generateWebhookSecret();
591
685
  const tokenArgs = opts.token && opts.url ? { token: opts.token, url: opts.url } : {};
686
+ const pairedArgs = opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {};
592
687
  writeCodexConfig({
593
688
  ...opts.configPath ? { configPath: opts.configPath } : {},
594
689
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
595
- webhookUrl: url,
596
- webhookSecret: secret,
690
+ ...url !== void 0 ? { webhookUrl: url } : {},
691
+ ...secret !== void 0 ? { webhookSecret: secret } : {},
597
692
  ...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
598
- ...tokenArgs
693
+ ...tokenArgs,
694
+ ...pairedArgs
599
695
  });
600
696
  recordRuntime("codex");
601
697
  const lines = [];
602
698
  lines.push("Configured runtime: codex");
603
- lines.push("Wake mode: webhook-sink + stop-hook peek (Codex has no channel injection).");
699
+ lines.push(
700
+ "Wake mode: self-contained (proxy-written pending marker) + stop-hook peek. Webhook OPTIONAL \u2014 an extra low-latency push path, never required."
701
+ );
604
702
  lines.push("");
605
703
  lines.push("Wrote [mcp_servers.kojee] to ~/.codex/config.toml:");
606
704
  lines.push(indent(buildCodexMcpServerTable({
607
- webhookUrl: url,
608
- webhookSecret: "<redacted>",
705
+ // In connectPairedMode the webhook env is left untouched — print neither key
706
+ // (they are preserved from the existing config, not owned this run).
707
+ ...url !== void 0 ? { webhookUrl: url } : {},
708
+ ...secret !== void 0 ? { webhookSecret: "<redacted>" } : {},
609
709
  ...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
610
710
  // Redact the gateway token in the human-readable printed copy (it ends up
611
711
  // in result.output → cli.ts console.error). The WRITTEN config above keeps
612
712
  // the real token; only this printed report is redacted, like webhookSecret.
613
- ...tokenArgs.token ? { token: "<redacted>", url: tokenArgs.url } : {}
713
+ ...tokenArgs.token ? { token: "<redacted>", url: tokenArgs.url } : {},
714
+ // A paired-config path is not a secret — print it verbatim.
715
+ ...pairedArgs
614
716
  })));
615
717
  if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
616
718
  lines.push("");
617
719
  lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
618
720
  lines.push(indent(buildCodexStopHookBlock()));
619
721
  lines.push("");
620
- lines.push(`webhook: ${wh.url ? wh.redactedSummary : "(receiver URL not set \u2014 fill KOJEE_WEBHOOK_URL above)"}`);
722
+ lines.push(
723
+ 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)"}`
724
+ );
621
725
  lines.push(`bounded-listen cap: ${CODEX_LISTEN_CAP_MS}ms (the model picks listen vs drain vs ignore).`);
622
726
  lines.push("");
623
727
  lines.push("Next steps (codex):");
624
728
  lines.push(" Restart Codex (or start a new `codex` / `codex exec` session) to load the MCP server and hook.");
625
- lines.push(" Stand up your webhook receiver at KOJEE_WEBHOOK_URL \u2014 contract:");
626
- lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
729
+ if (wh.url) {
730
+ lines.push(" Stand up your webhook receiver at KOJEE_WEBHOOK_URL \u2014 contract:");
731
+ lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
732
+ } else {
733
+ lines.push(" (Optional) For an extra low-latency push path, re-run with --webhook-url <receiver>; wake works without it.");
734
+ }
627
735
  lines.push(" Verify: kojee-mcp doctor");
628
736
  lines.push("");
629
737
  lines.push(CODEX_UNVERIFIED_NOTE);
@@ -652,7 +760,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
652
760
  return lines;
653
761
  }
654
762
  function distDir() {
655
- return path5.dirname(fileURLToPath(import.meta.url));
763
+ return path6.dirname(fileURLToPath(import.meta.url));
656
764
  }
657
765
  function resolveBinPath() {
658
766
  const entry = process.argv[1];
@@ -660,14 +768,30 @@ function resolveBinPath() {
660
768
  }
661
769
  function configureHermes(opts) {
662
770
  const runtime = "hermes";
663
- const wh = resolveWizardWebhook(opts);
771
+ const env0 = opts.env ?? process.env;
772
+ const suppliedUrl = (opts.webhookUrl ?? env0["KOJEE_WEBHOOK_URL"] ?? "").trim();
773
+ const effectiveOpts = suppliedUrl ? opts : { ...opts, webhookUrl: HERMES_DEFAULT_WEBHOOK_URL };
774
+ const wh = resolveWizardWebhook(effectiveOpts);
664
775
  if (wh.error) {
665
776
  return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
666
777
  }
778
+ opts = effectiveOpts;
667
779
  const lines = [];
668
780
  lines.push(`Configured runtime: ${runtime}`);
669
781
  lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
670
782
  lines.push("");
783
+ if (!suppliedUrl) {
784
+ lines.push(
785
+ "WARNING: no --webhook-url was supplied, so the webhook sink DEFAULTS to the fixed",
786
+ `loopback port ${HERMES_DEFAULT_WEBHOOK_URL} (the hermes-plugin listener). Every Tandem`,
787
+ "event body is POSTed there. On a multi-user host, another local user or process that",
788
+ "binds that port while the hermes plugin is down (before first start, or after a crash)",
789
+ "receives those event bodies plus valid signature/body pairs replayable against the real",
790
+ "receiver. If that risk applies to this host, re-run with an explicit --webhook-url",
791
+ "pointing at a receiver you control, and keep the hermes plugin running.",
792
+ ""
793
+ );
794
+ }
671
795
  if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
672
796
  if (wh.url) {
673
797
  const home = kojeeHomeDir();
@@ -697,7 +821,7 @@ function configureHermes(opts) {
697
821
  return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
698
822
  }
699
823
  recordRuntime(runtime);
700
- const envFile = path5.join(home, ".kojee", "hermes.env");
824
+ const envFile = path6.join(home, ".kojee", "hermes.env");
701
825
  lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
702
826
  lines.push("");
703
827
  lines.push(install.output);
@@ -713,30 +837,13 @@ function configureHermes(opts) {
713
837
  return { runtime, output: lines.join("\n"), exitCode: 0 };
714
838
  }
715
839
  function openclawConfigPath(opts) {
716
- return opts.openclawConfigPath ?? path5.join(kojeeHomeDir(), ".openclaw", "config.json");
717
- }
718
- function resolveOpenclawPluginSourceDir() {
719
- const candidates = [
720
- path5.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
721
- path5.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
722
- ];
723
- for (const dir of candidates) {
724
- if (fs4.existsSync(path5.join(dir, "openclaw.plugin.json"))) return dir;
725
- }
726
- return void 0;
840
+ return opts.openclawConfigPath ?? path6.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
727
841
  }
728
842
  function configureOpenclaw(opts) {
729
843
  const runtime = "openclaw";
730
- const env = opts.env ?? process.env;
731
- const token = (opts.token ?? "").trim();
732
- const url = (opts.url ?? env["KOJEE_GATEWAY_URL"] ?? "").trim();
733
- const pluginSourceDir = resolveOpenclawPluginSourceDir();
734
844
  const install = installOpenclaw({
735
- homeDir: kojeeHomeDir(),
736
845
  openclawConfigPath: openclawConfigPath(opts),
737
- ...url ? { url } : {},
738
- ...token ? { token } : {},
739
- ...pluginSourceDir ? { pluginSourceDir } : {}
846
+ ...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {}
740
847
  });
741
848
  recordRuntime(runtime);
742
849
  return { runtime, output: install.output, exitCode: install.exitCode };
@@ -745,7 +852,7 @@ async function runWizardUninstall(runtime, opts) {
745
852
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
746
853
  const lines = [`Uninstalling runtime: ${effective}`];
747
854
  if (effective === "claude-code") {
748
- const { runUninstall } = await import("./install-LJY2CHKG.js");
855
+ const { runUninstall } = await import("./install-V7LSQCYZ.js");
749
856
  const report = runUninstall({
750
857
  ...opts.configPath ? { configPath: opts.configPath } : {},
751
858
  ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
@@ -774,9 +881,9 @@ async function runWizardUninstall(runtime, opts) {
774
881
  } else {
775
882
  lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
776
883
  lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
777
- const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
884
+ const envPath = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
778
885
  try {
779
- fs4.unlinkSync(envPath);
886
+ fs5.unlinkSync(envPath);
780
887
  lines.push(` removed ${envPath}`);
781
888
  } catch {
782
889
  }
@@ -824,119 +931,13 @@ registerBuiltinInstaller(
824
931
  registerBuiltinInstaller(
825
932
  "openclaw",
826
933
  "OpenClaw",
827
- ["pair-credential", "in-process-plugin"],
934
+ ["pair-credential", "mcp-config-write"],
828
935
  (o) => configureOpenclaw(o)
829
936
  );
830
937
 
831
- // src/wizard/setup-handler.ts
832
- var SETUP_SUPPORTED_RUNTIMES = ["claude-code", "codex"];
833
- function isSetupRuntime(value) {
834
- return SETUP_SUPPORTED_RUNTIMES.includes(value);
835
- }
836
- function resolvePairCode(opts) {
837
- return opts.pair ?? opts.pairCode;
838
- }
839
- async function defaultRunPair(args) {
840
- const { runPair } = await import("./pair-P4ILCMT7.js");
841
- const { pairedConfigPath } = await import("./paired-config-JTFLHMZ2.js");
842
- const { defaultPairedKeystorePath } = await import("./keystore-XLEV3FL5.js");
843
- return runPair({
844
- code: args.code,
845
- url: args.url,
846
- keystorePath: defaultPairedKeystorePath(),
847
- configPath: pairedConfigPath()
848
- });
849
- }
850
- async function runSetup(opts) {
851
- const messages = [];
852
- const requested = opts.runtime ?? "claude-code";
853
- if (opts.verb === "setup" && opts.runtime !== void 0 && !isSetupRuntime(opts.runtime)) {
854
- const supported = SETUP_SUPPORTED_RUNTIMES.join(", ");
855
- const output = isWizardRuntime(opts.runtime) ? `\`setup\` does not support --runtime ${opts.runtime} yet (coming soon). Supported by \`setup\`: ${supported}. For ${opts.runtime}, use \`kojee-mcp init --runtime ${opts.runtime}\` for now.` : `Unknown --runtime "${opts.runtime}". \`setup\` supports: ${supported}. (For hermes/openclaw use \`kojee-mcp init\`.)`;
856
- return { runtime: requested, messages, output, exitCode: 2 };
857
- }
858
- if (opts.token !== void 0 && (opts.url === void 0 || opts.url.trim() === "") && !opts.uninstall) {
859
- return {
860
- runtime: requested,
861
- messages,
862
- output: "--token requires --url (the broker URL the Kojee UI gave you). Re-run with --url <broker-url>. Nothing was written.",
863
- exitCode: 2
864
- };
865
- }
866
- const url = opts.url ? opts.url.replace(/\/+$/, "") : void 0;
867
- if (opts.pairCode && !opts.uninstall) {
868
- const pair = opts.runPair ?? defaultRunPair;
869
- const pairUrl = url ?? DEFAULT_BROKER_URL;
870
- try {
871
- const r = await pair({ code: opts.pairCode, url: pairUrl });
872
- messages.push(r.message);
873
- } catch (err) {
874
- return {
875
- runtime: requested,
876
- messages,
877
- output: `Pairing failed: ${err.message}`,
878
- exitCode: 1
879
- };
880
- }
881
- }
882
- const loadCfg = opts.loadPairedConfig ?? loadPairedConfig;
883
- const hasCredential = loadCfg() !== null || opts.token !== void 0;
884
- if (!hasCredential && !opts.interactive && !opts.uninstall) {
885
- return {
886
- runtime: requested,
887
- messages,
888
- output: "Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run \u2014 or pass --pair <code> (or --token <T> --url <U>), or run in a terminal for the guided wizard.",
889
- exitCode: 1
890
- };
891
- }
892
- const result = await runWizard({
893
- ...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
894
- ...opts.uninstall ? { uninstall: true } : {},
895
- ...opts.configPath ? { configPath: opts.configPath } : {},
896
- ...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
897
- // Token mode (token + url) threads --token/--url into the written config.
898
- ...opts.token ? { token: opts.token } : {},
899
- ...url ? { url } : {},
900
- ...opts.webhookUrl ? { webhookUrl: opts.webhookUrl } : {},
901
- ...opts.webhookSecret ? { webhookSecret: opts.webhookSecret } : {},
902
- ...opts.webhookSignatureFormat ? { webhookSignatureFormat: opts.webhookSignatureFormat } : {},
903
- ...opts.webhookSignatureHeader ? { webhookSignatureHeader: opts.webhookSignatureHeader } : {},
904
- ...opts.webhookSignaturePrefix !== void 0 ? { webhookSignaturePrefix: opts.webhookSignaturePrefix } : {},
905
- interactive: opts.interactive,
906
- ...opts.interactive && opts.prompts ? opts.prompts : {}
907
- });
908
- if (opts.verb === "setup" && result.exitCode === 0 && !opts.uninstall) {
909
- return {
910
- runtime: result.runtime,
911
- messages,
912
- output: crispSetupSummary(result.runtime, opts, url),
913
- exitCode: 0
914
- };
915
- }
916
- return { runtime: result.runtime, messages, output: result.output, exitCode: result.exitCode };
917
- }
918
- function crispSetupSummary(runtime, opts, url) {
919
- const authLine = opts.token ? ` Auth: token mode (--token/--url${url ? `, ${url}` : ""})` : " Auth: paired credentials (~/.kojee/config.json)";
920
- const lines = [];
921
- if (runtime === "codex") {
922
- lines.push("\u2713 kojee is set up for Codex.");
923
- lines.push(` MCP config: ${opts.configPath ?? "~/.codex/config.toml"} ([mcp_servers.kojee])`);
924
- lines.push(` Stop hook: ${opts.hooksPath ?? "~/.codex/hooks.json"} (codex-stop)`);
925
- lines.push(authLine);
926
- lines.push("");
927
- lines.push("Next: restart Codex to connect.");
928
- } else {
929
- lines.push("\u2713 kojee is set up for Claude Code.");
930
- lines.push(` MCP config: ${opts.configPath ?? "~/.claude.json"} (mcpServers.kojee)`);
931
- lines.push(` Hooks: ${opts.hooksPath ?? "~/.claude/settings.json"} (Stop + UserPromptSubmit)`);
932
- lines.push(authLine);
933
- lines.push("");
934
- lines.push("Next: restart Claude Code to connect.");
935
- }
936
- return lines.join("\n");
937
- }
938
938
  export {
939
- SETUP_SUPPORTED_RUNTIMES,
940
- resolvePairCode,
941
- runSetup
939
+ reconcileConnectPairSlot,
940
+ pairSlotFor,
941
+ DEFAULT_BROKER_URL,
942
+ runWizard
942
943
  };