kojee-mcp 0.5.13 → 0.5.14

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.
@@ -29,16 +29,303 @@ import {
29
29
  } from "./chunk-X672ZN7V.js";
30
30
 
31
31
  // src/wizard/wizard.ts
32
+ import crypto2 from "crypto";
33
+ import fs4 from "fs";
34
+ import path5 from "path";
35
+ import { fileURLToPath } from "url";
36
+
37
+ // src/wizard/registry.ts
38
+ var installers = /* @__PURE__ */ new Map();
39
+ function register(installer) {
40
+ installers.set(installer.id, installer);
41
+ }
42
+ function getInstaller(id) {
43
+ return installers.get(id);
44
+ }
45
+ function runtimeUsesWebhook(id) {
46
+ return getInstaller(id)?.capabilities.includes("webhook") ?? false;
47
+ }
48
+
49
+ // src/wizard/capabilities/webhook-secret.ts
32
50
  import crypto from "crypto";
33
51
  import fs from "fs";
34
52
  import path from "path";
53
+ function generateSecret() {
54
+ return crypto.randomBytes(32).toString("hex");
55
+ }
56
+ function readSecretFromEnv(envPath) {
57
+ let body;
58
+ try {
59
+ body = fs.readFileSync(envPath, "utf8");
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ const m = body.match(/^(?:export\s+)?KOJEE_WEBHOOK_SECRET=(.*)$/m);
64
+ if (!m) return void 0;
65
+ const raw = m[1].trim().replace(/^['"]|['"]$/g, "");
66
+ return raw.length > 0 ? raw : void 0;
67
+ }
68
+ function shellSingleQuote(value) {
69
+ return `'${value.replace(/'/g, `'\\''`)}'`;
70
+ }
71
+ function upsertEnvFile(filePath, vars, opts = {}) {
72
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
73
+ let lines = [];
74
+ try {
75
+ lines = fs.readFileSync(filePath, "utf8").split("\n");
76
+ } catch {
77
+ }
78
+ if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
79
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
80
+ const pre = opts.exportPrefix ? "export " : "";
81
+ for (const [k, v] of vars) {
82
+ const line = `${pre}${k}=${shellSingleQuote(v)}`;
83
+ const idx = lines.findIndex((l) => l.startsWith(`${k}=`) || l.startsWith(`export ${k}=`));
84
+ if (idx >= 0) lines[idx] = line;
85
+ else lines.push(line);
86
+ }
87
+ fs.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
88
+ }
89
+ function writeWebhookSecretBothSides(opts) {
90
+ const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
91
+ const secret = existing ?? generateSecret();
92
+ const reused = existing !== void 0;
93
+ const daemonVars = [
94
+ ["KOJEE_RUNTIME", opts.runtime],
95
+ ["KOJEE_WEBHOOK_URL", opts.webhookUrl],
96
+ ["KOJEE_WEBHOOK_SECRET", secret],
97
+ ...opts.signatureEnv ?? []
98
+ ];
99
+ upsertEnvFile(opts.daemonEnvPath, daemonVars, {
100
+ exportPrefix: true,
101
+ header: `# kojee daemon env for runtime=${opts.runtime} (source this before starting the daemon)`
102
+ });
103
+ secureFile(opts.daemonEnvPath);
104
+ const adapterVars = [
105
+ ["KOJEE_WEBHOOK_SECRET", secret],
106
+ ...opts.mcpDir ? [["KOJEE_MCP_DIR", opts.mcpDir]] : [],
107
+ ...opts.allowAllUsers ? [["KOJEE_TANDEM_ALLOW_ALL_USERS", "true"]] : []
108
+ ];
109
+ upsertEnvFile(opts.adapterEnvPath, adapterVars);
110
+ secureFile(opts.adapterEnvPath);
111
+ return { secret, reused, daemonEnvPath: opts.daemonEnvPath, adapterEnvPath: opts.adapterEnvPath };
112
+ }
113
+
114
+ // src/wizard/installers/hermes.ts
115
+ import path4 from "path";
116
+
117
+ // src/wizard/plugin-payload.ts
118
+ import fs2 from "fs";
119
+ import path2 from "path";
120
+ var HERMES_PAYLOAD_FILES = [
121
+ "__init__.py",
122
+ "adapter.py",
123
+ "kojee_tandem_core.py",
124
+ "plugin.yaml"
125
+ ];
126
+ function stagePluginPayload(opts) {
127
+ fs2.mkdirSync(opts.destDir, { recursive: true });
128
+ const written = [];
129
+ for (const file of opts.files) {
130
+ const src = path2.join(opts.srcDir, file);
131
+ if (!fs2.existsSync(src)) {
132
+ throw new Error(
133
+ `stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
134
+ );
135
+ }
136
+ const dest = path2.join(opts.destDir, file);
137
+ fs2.copyFileSync(src, dest);
138
+ written.push(dest);
139
+ }
140
+ return written;
141
+ }
142
+ function resolveBundledPayloadDir(runtime, baseDir) {
143
+ const dir = path2.join(baseDir, "plugins", runtime);
144
+ if (!fs2.existsSync(dir) || !fs2.statSync(dir).isDirectory()) {
145
+ throw new Error(
146
+ `resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
147
+ );
148
+ }
149
+ return dir;
150
+ }
151
+ function missingPayloadFiles(runtime, baseDir, files) {
152
+ const dir = path2.join(baseDir, "plugins", runtime);
153
+ const dirOk = fs2.existsSync(dir) && fs2.statSync(dir).isDirectory();
154
+ if (!dirOk) return [...files];
155
+ return files.filter((f) => !fs2.existsSync(path2.join(dir, f)));
156
+ }
157
+ function installBundledPayload(opts) {
158
+ const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
159
+ return stagePluginPayload({ srcDir, destDir: opts.destDir, files: opts.files });
160
+ }
161
+
162
+ // src/wizard/service.ts
163
+ import fs3 from "fs";
164
+ import path3 from "path";
165
+ function systemdUnit(spec) {
166
+ return [
167
+ "[Unit]",
168
+ `Description=kojee-mcp daemon (${spec.runtime} runtime) \u2014 Tandem wake sidecar`,
169
+ "After=network-online.target",
170
+ "Wants=network-online.target",
171
+ "",
172
+ "[Service]",
173
+ "Type=simple",
174
+ `EnvironmentFile=${spec.envFile}`,
175
+ `Environment=KOJEE_RUNTIME=${spec.runtime}`,
176
+ `ExecStart=${spec.binPath}`,
177
+ "Restart=always",
178
+ "RestartSec=5",
179
+ "StandardOutput=journal",
180
+ "StandardError=journal",
181
+ "",
182
+ "[Install]",
183
+ "WantedBy=default.target",
184
+ ""
185
+ ].join("\n");
186
+ }
187
+ function darwinLabel(serviceName) {
188
+ return `net.kojee.${serviceName}`;
189
+ }
190
+ function launchdPlist(spec, label) {
191
+ return [
192
+ '<?xml version="1.0" encoding="UTF-8"?>',
193
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
194
+ '<plist version="1.0">',
195
+ "<dict>",
196
+ " <key>Label</key>",
197
+ ` <string>${label}</string>`,
198
+ " <key>ProgramArguments</key>",
199
+ ` <array><string>${spec.binPath}</string></array>`,
200
+ " <key>EnvironmentVariables</key>",
201
+ ` <dict><key>KOJEE_RUNTIME</key><string>${spec.runtime}</string></dict>`,
202
+ " <key>RunAtLoad</key><true/>",
203
+ " <key>KeepAlive</key><true/>",
204
+ "</dict>",
205
+ "</plist>",
206
+ ""
207
+ ].join("\n");
208
+ }
209
+ function planService(platform, spec) {
210
+ if (platform === "linux") {
211
+ const unitPath = path3.join(
212
+ spec.homeDir,
213
+ ".config",
214
+ "systemd",
215
+ "user",
216
+ `${spec.serviceName}.service`
217
+ );
218
+ return {
219
+ supported: true,
220
+ unitPath,
221
+ unitContent: systemdUnit(spec),
222
+ activateCmd: `systemctl --user daemon-reload && systemctl --user enable --now ${spec.serviceName}`,
223
+ deactivateCmd: `systemctl --user disable --now ${spec.serviceName}`
224
+ };
225
+ }
226
+ if (platform === "darwin") {
227
+ const label = darwinLabel(spec.serviceName);
228
+ const unitPath = path3.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
229
+ return {
230
+ supported: true,
231
+ unitPath,
232
+ unitContent: launchdPlist(spec, label),
233
+ activateCmd: `launchctl load -w ${unitPath}`,
234
+ deactivateCmd: `launchctl unload -w ${unitPath}`
235
+ };
236
+ }
237
+ const manual = `schtasks /create /tn ${spec.serviceName} /sc onlogon /tr "${spec.binPath}" /f (wrap it to set KOJEE_RUNTIME=${spec.runtime} and load ${spec.envFile}), or run directly: KOJEE_RUNTIME=${spec.runtime} ${spec.binPath}`;
238
+ return {
239
+ supported: false,
240
+ activateCmd: manual,
241
+ deactivateCmd: `schtasks /delete /tn ${spec.serviceName} /f`,
242
+ note: "Auto-service install isn't supported on this platform yet; run the command above to keep the daemon up."
243
+ };
244
+ }
245
+ function writeService(platform, spec) {
246
+ const plan = planService(platform, spec);
247
+ if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
248
+ fs3.mkdirSync(path3.dirname(plan.unitPath), { recursive: true });
249
+ fs3.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
250
+ }
251
+ return plan;
252
+ }
253
+
254
+ // src/wizard/installers/hermes.ts
255
+ function installHermes(inp) {
256
+ const daemonEnv = path4.join(inp.homeDir, ".kojee", "hermes.env");
257
+ const adapterEnv = path4.join(inp.homeDir, ".hermes", ".env");
258
+ const pluginDir = path4.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
259
+ if (!inp.skipPayload) {
260
+ const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
261
+ if (missing.length > 0) {
262
+ return {
263
+ runtime: "hermes",
264
+ 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.`,
265
+ exitCode: 2,
266
+ secret: "",
267
+ secretReused: false
268
+ };
269
+ }
270
+ }
271
+ const sec = writeWebhookSecretBothSides({
272
+ daemonEnvPath: daemonEnv,
273
+ adapterEnvPath: adapterEnv,
274
+ webhookUrl: inp.webhookUrl,
275
+ runtime: "hermes",
276
+ ...inp.mcpDir ? { mcpDir: inp.mcpDir } : {},
277
+ ...inp.allowAllUsers ? { allowAllUsers: true } : {},
278
+ ...inp.signatureEnv && inp.signatureEnv.length > 0 ? { signatureEnv: inp.signatureEnv } : {},
279
+ ...inp.webhookSecret ? { existingSecret: inp.webhookSecret } : {}
280
+ });
281
+ const staged = inp.skipPayload ? [] : installBundledPayload({
282
+ runtime: "hermes",
283
+ baseDir: inp.payloadBaseDir,
284
+ destDir: pluginDir,
285
+ files: [...HERMES_PAYLOAD_FILES]
286
+ });
287
+ const svc = writeService(inp.platform, {
288
+ serviceName: "kojee-hermes",
289
+ binPath: inp.binPath,
290
+ envFile: daemonEnv,
291
+ runtime: "hermes",
292
+ homeDir: inp.homeDir
293
+ });
294
+ const lines = [
295
+ "Configured runtime: hermes (complete install)",
296
+ ` secret: ${sec.reused ? "reused existing" : "generated"} (matched on daemon + adapter env)`,
297
+ ` daemon env: ${daemonEnv}`,
298
+ ` adapter env: ${adapterEnv}`,
299
+ inp.skipPayload ? ` plugin: SKIPPED \u2014 no bundled payload (run \`npm run build\` to stage dist/plugins/hermes)` : ` plugin: ${pluginDir} (${staged.length} files)`,
300
+ svc.supported ? ` service: ${svc.unitPath}` : ` service: manual \u2014 ${svc.note ?? "unsupported platform"}`,
301
+ // NB: the "Receiver contract" section is intentionally NOT printed here.
302
+ // configureHermes prints buildDaemonEnvBlock (the single contract owner for
303
+ // every daemon runtime) on the same URL-present path before this output, so
304
+ // emitting it here too would print the contract twice.
305
+ "",
306
+ "Next:",
307
+ ` - start the daemon service: ${svc.activateCmd}`,
308
+ " - reload the Hermes gateway to load the plugin: systemctl --user restart hermes-gateway (or: hermes gateway restart)",
309
+ " - join a tandem once: tandem_join <tandem_id>",
310
+ " - verify: kojee-mcp doctor"
311
+ ];
312
+ return {
313
+ runtime: "hermes",
314
+ output: lines.join("\n"),
315
+ exitCode: 0,
316
+ secret: sec.secret,
317
+ secretReused: sec.reused
318
+ };
319
+ }
320
+
321
+ // src/wizard/wizard.ts
35
322
  var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
36
323
  function generateWebhookSecret() {
37
- return crypto.randomBytes(32).toString("hex");
324
+ return crypto2.randomBytes(32).toString("hex");
38
325
  }
39
326
  async function resolveRuntime(opts) {
40
327
  if (opts.runtime !== void 0) {
41
- if (!isWizardRuntime(opts.runtime)) {
328
+ if (!isWizardRuntime(opts.runtime) && getInstaller(opts.runtime) === void 0) {
42
329
  return {
43
330
  error: `Unknown --runtime "${opts.runtime}". Expected one of: ${WIZARD_RUNTIMES.join(", ")}.`
44
331
  };
@@ -47,7 +334,7 @@ async function resolveRuntime(opts) {
47
334
  }
48
335
  if (opts.interactive && opts.promptRuntime) {
49
336
  const picked = await opts.promptRuntime();
50
- if (!isWizardRuntime(picked)) {
337
+ if (!isWizardRuntime(picked) && getInstaller(picked) === void 0) {
51
338
  return { error: `Unknown runtime "${picked}". Expected one of: ${WIZARD_RUNTIMES.join(", ")}.` };
52
339
  }
53
340
  return { runtime: picked };
@@ -96,11 +383,8 @@ function resolveWizardWebhook(opts) {
96
383
  ...warning !== void 0 ? { warning } : {}
97
384
  };
98
385
  }
99
- function shellSingleQuote(value) {
100
- return `'${value.replace(/'/g, `'\\''`)}'`;
101
- }
102
386
  function writeRuntimeEnvFile(runtime, url, secret, signatureEnv = []) {
103
- const envPath = path.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
387
+ const envPath = path5.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
104
388
  const body = [
105
389
  `# kojee daemon env for runtime=${runtime} (source this before starting the daemon)`,
106
390
  `export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`,
@@ -110,15 +394,12 @@ function writeRuntimeEnvFile(runtime, url, secret, signatureEnv = []) {
110
394
  ...signatureEnv.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`),
111
395
  ""
112
396
  ].join("\n");
113
- fs.mkdirSync(path.dirname(envPath), { recursive: true, mode: 448 });
114
- fs.writeFileSync(envPath, body, { mode: 384 });
397
+ fs4.mkdirSync(path5.dirname(envPath), { recursive: true, mode: 448 });
398
+ fs4.writeFileSync(envPath, body, { mode: 384 });
115
399
  secureFile(envPath);
116
400
  return envPath;
117
401
  }
118
402
  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.";
119
- function runtimeUsesWebhook(runtime) {
120
- return runtime === "codex" || runtime === "hermes" || runtime === "openclaw";
121
- }
122
403
  async function gatherGuidedInputs(runtime, opts) {
123
404
  const preamble = [];
124
405
  const next = { ...opts };
@@ -178,14 +459,16 @@ async function runWizard(opts) {
178
459
  effective = gathered.opts;
179
460
  preamble = gathered.preamble;
180
461
  }
181
- const result = runtime === "claude-code" ? await configureClaudeCode(effective) : runtime === "codex" ? configureCodex(effective) : configureWebhookDaemon(runtime, effective);
462
+ const installer = getInstaller(runtime);
463
+ const result = installer ? await installer.install({ opts: effective }) : { runtime, output: `No installer registered for runtime '${runtime}'`, exitCode: 2 };
182
464
  if (preamble.length > 0 && result.exitCode === 0) {
183
465
  return {
184
- ...result,
185
- output: [...preamble, "", result.output, "", whatHappensNext(runtime, effective)].join("\n")
466
+ runtime,
467
+ output: [...preamble, "", result.output, "", whatHappensNext(runtime, effective)].join("\n"),
468
+ exitCode: result.exitCode
186
469
  };
187
470
  }
188
- return result;
471
+ return { runtime, output: result.output, exitCode: result.exitCode };
189
472
  }
190
473
  function whatHappensNext(runtime, opts) {
191
474
  const lines = ["What happens next:"];
@@ -294,19 +577,9 @@ function configureCodex(opts) {
294
577
  lines.push(CODEX_UNVERIFIED_NOTE);
295
578
  return { runtime: "codex", output: lines.join("\n"), exitCode: 0 };
296
579
  }
297
- function configureWebhookDaemon(runtime, opts) {
298
- const wh = resolveWizardWebhook(opts);
299
- if (wh.error) {
300
- return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
301
- }
302
- recordRuntime(runtime);
580
+ function buildDaemonEnvBlock(runtime, wh, envFile) {
303
581
  const lines = [];
304
- lines.push(`Configured runtime: ${runtime}`);
305
- lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
306
- lines.push("");
307
- if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
308
- if (wh.url) {
309
- const envFile = writeRuntimeEnvFile(runtime, wh.url, wh.secret, wh.signatureEnv);
582
+ if (wh.url && envFile) {
310
583
  lines.push("Export these before starting the daemon (also written to a source-able file):");
311
584
  lines.push(` export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`);
312
585
  lines.push(` export KOJEE_WEBHOOK_URL=${shellSingleQuote(wh.url)}`);
@@ -324,11 +597,87 @@ function configureWebhookDaemon(runtime, opts) {
324
597
  lines.push("");
325
598
  lines.push("Receiver contract:");
326
599
  lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
600
+ return lines;
601
+ }
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));
327
615
  lines.push("");
328
616
  lines.push(`Next steps (${runtime}):`);
329
617
  lines.push(" Start the daemon with the env above. Verify: kojee-mcp doctor (after the daemon is up).");
330
618
  return { runtime, output: lines.join("\n"), exitCode: 0 };
331
619
  }
620
+ function distDir() {
621
+ return path5.dirname(fileURLToPath(import.meta.url));
622
+ }
623
+ function resolveBinPath() {
624
+ const entry = process.argv[1];
625
+ return entry && entry.length > 0 ? entry : "kojee-mcp";
626
+ }
627
+ function configureHermes(opts) {
628
+ const runtime = "hermes";
629
+ const wh = resolveWizardWebhook(opts);
630
+ if (wh.error) {
631
+ return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
632
+ }
633
+ const lines = [];
634
+ lines.push(`Configured runtime: ${runtime}`);
635
+ lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
636
+ lines.push("");
637
+ if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
638
+ if (wh.url) {
639
+ const home = kojeeHomeDir();
640
+ let skipPayload = false;
641
+ const base = distDir();
642
+ try {
643
+ resolveBundledPayloadDir("hermes", base);
644
+ } catch {
645
+ skipPayload = true;
646
+ }
647
+ const env = opts.env ?? process.env;
648
+ const suppliedSecret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
649
+ const install = installHermes({
650
+ homeDir: home,
651
+ payloadBaseDir: base,
652
+ platform: process.platform,
653
+ binPath: resolveBinPath(),
654
+ webhookUrl: wh.url,
655
+ ...suppliedSecret ? { webhookSecret: suppliedSecret } : {},
656
+ ...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
657
+ signatureHeader: wh.signatureHeader,
658
+ signaturePrefix: wh.signaturePrefix,
659
+ skipPayload
660
+ });
661
+ if (install.exitCode !== 0) {
662
+ lines.push(install.output);
663
+ return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
664
+ }
665
+ recordRuntime(runtime);
666
+ const envFile = path5.join(home, ".kojee", "hermes.env");
667
+ lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
668
+ lines.push("");
669
+ lines.push(install.output);
670
+ return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
671
+ }
672
+ recordRuntime(runtime);
673
+ lines.push(...buildDaemonEnvBlock(runtime, wh, void 0));
674
+ lines.push("");
675
+ lines.push(`Next steps (${runtime}):`);
676
+ lines.push(" Re-run with --webhook-url once your receiver is up to complete the install");
677
+ lines.push(" (writes both env files, copies the plugin, installs the daemon service).");
678
+ lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
679
+ return { runtime, output: lines.join("\n"), exitCode: 0 };
680
+ }
332
681
  async function runWizardUninstall(runtime, opts) {
333
682
  const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
334
683
  const lines = [`Uninstalling runtime: ${effective}`];
@@ -359,9 +708,9 @@ async function runWizardUninstall(runtime, opts) {
359
708
  } else {
360
709
  lines.push(" (hermes/openclaw write no MCP-config or hooks \u2014 nothing to tear down.");
361
710
  lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
362
- const envPath = path.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
711
+ const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
363
712
  try {
364
- fs.unlinkSync(envPath);
713
+ fs4.unlinkSync(envPath);
365
714
  lines.push(` removed ${envPath}`);
366
715
  } catch {
367
716
  }
@@ -372,6 +721,46 @@ async function runWizardUninstall(runtime, opts) {
372
721
  function indent(s) {
373
722
  return s.split("\n").map((l) => " " + l).join("\n");
374
723
  }
724
+ function registerBuiltinInstaller(id, menuLabel, capabilities, doInstall) {
725
+ register({
726
+ id,
727
+ menuLabel,
728
+ capabilities,
729
+ install: async (ctx) => doInstall(ctx.opts),
730
+ uninstall: async (ctx) => runWizardUninstall(id, ctx.opts),
731
+ verify: async () => ({ runtime: id, ok: true, checks: [] })
732
+ });
733
+ }
734
+ registerBuiltinInstaller(
735
+ "claude-code",
736
+ "Claude Code",
737
+ ["pair-credential", "mcp-config-write", "hooks-write"],
738
+ (o) => configureClaudeCode(o)
739
+ );
740
+ registerBuiltinInstaller(
741
+ "codex",
742
+ "Codex",
743
+ ["pair-credential", "mcp-config-write", "hooks-write", "webhook"],
744
+ (o) => configureCodex(o)
745
+ );
746
+ registerBuiltinInstaller(
747
+ "hermes",
748
+ "Hermes",
749
+ [
750
+ "pair-credential",
751
+ "webhook",
752
+ "webhook-secret-both-sides",
753
+ "plugin-payload-copy",
754
+ "service-install"
755
+ ],
756
+ (o) => configureHermes(o)
757
+ );
758
+ registerBuiltinInstaller(
759
+ "openclaw",
760
+ "OpenClaw",
761
+ ["pair-credential", "webhook"],
762
+ (o) => configureWebhookDaemon("openclaw", o)
763
+ );
375
764
  export {
376
765
  CODEX_UNVERIFIED_NOTE,
377
766
  DEFAULT_BROKER_URL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kojee-mcp",
3
- "version": "0.5.13",
3
+ "version": "0.5.14",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "_buildNote": "src/version.ts resolves package.json via '../package.json' from import.meta.url; this assumes dist output stays flat one level under the package root (tsup default outDir=dist, no nesting). If the build adds outDir nesting or a deeper entry, update version.ts's relative path.",
18
18
  "scripts": {
19
- "build": "tsup src/cli.ts src/index.ts src/lib.ts --format esm --dts --clean",
19
+ "build": "tsup src/cli.ts src/index.ts src/lib.ts --format esm --dts --clean && tsx scripts/bundle-plugins.ts",
20
20
  "prepublishOnly": "npm run build && node scripts/verify-tarball.mjs",
21
21
  "dev": "tsup src/cli.ts --format esm --watch",
22
22
  "typecheck": "tsc --noEmit",