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.
- package/dist/cli.js +1 -1
- package/dist/plugins/hermes/__init__.py +21 -0
- package/dist/plugins/hermes/adapter.py +481 -0
- package/dist/plugins/hermes/kojee_tandem_core.py +344 -0
- package/dist/plugins/hermes/plugin.yaml +58 -0
- package/dist/{wizard-L4MYRLJI.js → wizard-5ILBK6YD.js} +419 -30
- package/package.json +2 -2
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
114
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
711
|
+
const envPath = path5.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
363
712
|
try {
|
|
364
|
-
|
|
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.
|
|
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",
|