kojee-mcp 0.5.19 → 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.
- package/README.md +13 -8
- package/dist/ancestry-A2F5KQ6A.js +10 -0
- package/dist/cc-session-id-RURNIHHC.js +10 -0
- package/dist/{chunk-U4MNICQI.js → chunk-247WFMCJ.js} +12 -22
- package/dist/{chunk-PSVVRGJC.js → chunk-5H75AEZ2.js} +22 -9
- package/dist/{chunk-YH27B6SW.js → chunk-5SZHXYPK.js} +16 -3
- package/dist/{chunk-DO42NPNR.js → chunk-67F67AQ6.js} +14 -3
- package/dist/{chunk-CH32ELFX.js → chunk-6G6YYST6.js} +1 -1
- package/dist/chunk-D6JKFJ6A.js +11 -0
- package/dist/chunk-E6WMFMM2.js +100 -0
- package/dist/chunk-EIAUW6KO.js +82 -0
- package/dist/chunk-EIH2LNF4.js +45 -0
- package/dist/{chunk-74XFVX6Z.js → chunk-FSKGQ6GT.js} +47 -10
- package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
- package/dist/{wizard-Y3ULSO2C.js → chunk-GVWYW7MY.js} +247 -138
- package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
- package/dist/chunk-KNEJTD6G.js +31 -0
- package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
- package/dist/chunk-NE2F5CKS.js +113 -0
- package/dist/{chunk-XXFVWP6H.js → chunk-OB5T6P24.js} +1 -1
- package/dist/{chunk-65KRRDHP.js → chunk-OBIJ6WQP.js} +82 -27
- package/dist/{chunk-DXJ6QLSJ.js → chunk-PKOBR7ZN.js} +1 -1
- package/dist/{chunk-2OLXXOT3.js → chunk-PPTKGWFF.js} +69 -3
- package/dist/{chunk-X672ZN7V.js → chunk-QEJUNP3X.js} +3 -2
- package/dist/chunk-QJFMU4QC.js +33 -0
- package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
- package/dist/{chunk-XIDCYUTK.js → chunk-SGRVG4HW.js} +9 -4
- package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
- package/dist/chunk-U5HHHRXA.js +103 -0
- package/dist/chunk-WBXU27BF.js +35 -0
- package/dist/chunk-WIU5WGDF.js +10 -0
- package/dist/chunk-XJEBJIQE.js +52 -0
- package/dist/chunk-Z5LPNJQ6.js +20 -0
- package/dist/cli.js +121 -89
- package/dist/{chunk-35XBRG3V.js → codex-stop-hook-EOMQD3J4.js} +11 -15
- package/dist/connect-handler-HIRM624N.js +137 -0
- package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
- package/dist/{doctor-U3MTB4GY.js → doctor-PLKLHTJM.js} +48 -22
- package/dist/{doctor-codex-PA3WO6LR.js → doctor-codex-HARAAH4Y.js} +23 -6
- package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
- package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
- package/dist/{event-stream-XX5EZ6HN.js → event-stream-KRYWEYWO.js} +7 -4
- package/dist/{hook-server-XK2NHLJV.js → hook-server-CWBH6PFL.js} +2 -2
- package/dist/index.js +13 -11
- package/dist/{install-LJY2CHKG.js → install-V7LSQCYZ.js} +37 -22
- package/dist/{keystore-XLEV3FL5.js → keystore-LQNFDHGO.js} +2 -2
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +12 -9
- package/dist/pair-HTCVSMR6.js +11 -0
- package/dist/{paired-config-JTFLHMZ2.js → paired-config-GUDDO2CH.js} +4 -2
- package/dist/{parent-watchdog-TLU355FB.js → parent-watchdog-LTUNF7PD.js} +24 -5
- package/dist/{registry-CGLE5X54.js → registry-VRFHKOPP.js} +195 -26
- package/dist/resolve-slot-BFIB46Y5.js +13 -0
- package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
- package/dist/{send-cli-IPADRDD2.js → send-cli-45RLGYIC.js} +24 -12
- package/dist/{server-LTFG4GMV.js → server-YTHOMA4H.js} +5 -3
- package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
- package/dist/setup-handler-UHVKSX7E.js +159 -0
- package/dist/{stop-hook-QZTFH5WV.js → stop-hook-YI4KRKF2.js} +17 -20
- package/dist/{tail-stream-CEJZGSIR.js → tail-stream-NBGHHBS4.js} +61 -22
- package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
- package/package.json +2 -1
- package/dist/ancestry-ONFBQEP5.js +0 -8
- package/dist/chunk-BLEGIR35.js +0 -43
- package/dist/chunk-CLKCNV2A.js +0 -100
- package/dist/chunk-OGHDTFAX.js +0 -50
- package/dist/chunk-VHKPWUX7.js +0 -41
- package/dist/codex-stop-hook-VY7DOMAG.js +0 -16
- package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
- package/dist/pair-P4ILCMT7.js +0 -10
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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-
|
|
10
|
+
} from "./chunk-OBIJ6WQP.js";
|
|
12
11
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
CONNECT_RUNTIMES
|
|
13
|
+
} from "./chunk-EIH2LNF4.js";
|
|
14
|
+
import {
|
|
15
|
+
clearRuntimeRecord,
|
|
16
|
+
readRecordedRuntime,
|
|
17
|
+
recordRuntime
|
|
18
|
+
} from "./chunk-RDLF4NQC.js";
|
|
17
19
|
import {
|
|
18
20
|
kojeeHomeDir
|
|
19
21
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -26,17 +28,109 @@ import {
|
|
|
26
28
|
resolveWebhookConfig
|
|
27
29
|
} from "./chunk-V5VZPYMZ.js";
|
|
28
30
|
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
loadPairedConfig,
|
|
32
|
+
pairedConfigPath,
|
|
33
|
+
perRuntimeConfigPath
|
|
34
|
+
} from "./chunk-5SZHXYPK.js";
|
|
35
|
+
import {
|
|
36
|
+
defaultPairedKeystorePath,
|
|
37
|
+
deriveKeystorePath
|
|
38
|
+
} from "./chunk-6G6YYST6.js";
|
|
39
|
+
import {
|
|
40
|
+
secureFile,
|
|
41
|
+
unlinkSyncRetry
|
|
42
|
+
} from "./chunk-U5HHHRXA.js";
|
|
31
43
|
import {
|
|
32
44
|
CODEX_LISTEN_CAP_MS,
|
|
33
45
|
buildWebhookReceiverNote
|
|
34
|
-
} from "./chunk-
|
|
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
|
+
}
|
|
35
129
|
|
|
36
130
|
// src/wizard/wizard.ts
|
|
37
131
|
import crypto2 from "crypto";
|
|
38
|
-
import
|
|
39
|
-
import
|
|
132
|
+
import fs5 from "fs";
|
|
133
|
+
import path6 from "path";
|
|
40
134
|
import { fileURLToPath } from "url";
|
|
41
135
|
|
|
42
136
|
// src/wizard/registry.ts
|
|
@@ -53,15 +147,15 @@ function runtimeUsesWebhook(id) {
|
|
|
53
147
|
|
|
54
148
|
// src/wizard/capabilities/webhook-secret.ts
|
|
55
149
|
import crypto from "crypto";
|
|
56
|
-
import
|
|
57
|
-
import
|
|
150
|
+
import fs2 from "fs";
|
|
151
|
+
import path2 from "path";
|
|
58
152
|
function generateSecret() {
|
|
59
153
|
return crypto.randomBytes(32).toString("hex");
|
|
60
154
|
}
|
|
61
155
|
function readSecretFromEnv(envPath) {
|
|
62
156
|
let body;
|
|
63
157
|
try {
|
|
64
|
-
body =
|
|
158
|
+
body = fs2.readFileSync(envPath, "utf8");
|
|
65
159
|
} catch {
|
|
66
160
|
return void 0;
|
|
67
161
|
}
|
|
@@ -74,10 +168,10 @@ function shellSingleQuote(value) {
|
|
|
74
168
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
75
169
|
}
|
|
76
170
|
function upsertEnvFile(filePath, vars, opts = {}) {
|
|
77
|
-
|
|
171
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
78
172
|
let lines = [];
|
|
79
173
|
try {
|
|
80
|
-
lines =
|
|
174
|
+
lines = fs2.readFileSync(filePath, "utf8").split("\n");
|
|
81
175
|
} catch {
|
|
82
176
|
}
|
|
83
177
|
if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
|
|
@@ -89,7 +183,7 @@ function upsertEnvFile(filePath, vars, opts = {}) {
|
|
|
89
183
|
if (idx >= 0) lines[idx] = line;
|
|
90
184
|
else lines.push(line);
|
|
91
185
|
}
|
|
92
|
-
|
|
186
|
+
fs2.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
|
|
93
187
|
}
|
|
94
188
|
function writeWebhookSecretBothSides(opts) {
|
|
95
189
|
const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
|
|
@@ -117,11 +211,11 @@ function writeWebhookSecretBothSides(opts) {
|
|
|
117
211
|
}
|
|
118
212
|
|
|
119
213
|
// src/wizard/installers/hermes.ts
|
|
120
|
-
import
|
|
214
|
+
import path5 from "path";
|
|
121
215
|
|
|
122
216
|
// src/wizard/plugin-payload.ts
|
|
123
|
-
import
|
|
124
|
-
import
|
|
217
|
+
import fs3 from "fs";
|
|
218
|
+
import path3 from "path";
|
|
125
219
|
var HERMES_PAYLOAD_FILES = [
|
|
126
220
|
"__init__.py",
|
|
127
221
|
"adapter.py",
|
|
@@ -129,24 +223,24 @@ var HERMES_PAYLOAD_FILES = [
|
|
|
129
223
|
"plugin.yaml"
|
|
130
224
|
];
|
|
131
225
|
function stagePluginPayload(opts) {
|
|
132
|
-
|
|
226
|
+
fs3.mkdirSync(opts.destDir, { recursive: true });
|
|
133
227
|
const written = [];
|
|
134
228
|
for (const file of opts.files) {
|
|
135
|
-
const src =
|
|
136
|
-
if (!
|
|
229
|
+
const src = path3.join(opts.srcDir, file);
|
|
230
|
+
if (!fs3.existsSync(src)) {
|
|
137
231
|
throw new Error(
|
|
138
232
|
`stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
|
|
139
233
|
);
|
|
140
234
|
}
|
|
141
|
-
const dest =
|
|
142
|
-
|
|
235
|
+
const dest = path3.join(opts.destDir, file);
|
|
236
|
+
fs3.copyFileSync(src, dest);
|
|
143
237
|
written.push(dest);
|
|
144
238
|
}
|
|
145
239
|
return written;
|
|
146
240
|
}
|
|
147
241
|
function resolveBundledPayloadDir(runtime, baseDir) {
|
|
148
|
-
const dir =
|
|
149
|
-
if (!
|
|
242
|
+
const dir = path3.join(baseDir, "plugins", runtime);
|
|
243
|
+
if (!fs3.existsSync(dir) || !fs3.statSync(dir).isDirectory()) {
|
|
150
244
|
throw new Error(
|
|
151
245
|
`resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
|
|
152
246
|
);
|
|
@@ -154,10 +248,10 @@ function resolveBundledPayloadDir(runtime, baseDir) {
|
|
|
154
248
|
return dir;
|
|
155
249
|
}
|
|
156
250
|
function missingPayloadFiles(runtime, baseDir, files) {
|
|
157
|
-
const dir =
|
|
158
|
-
const dirOk =
|
|
251
|
+
const dir = path3.join(baseDir, "plugins", runtime);
|
|
252
|
+
const dirOk = fs3.existsSync(dir) && fs3.statSync(dir).isDirectory();
|
|
159
253
|
if (!dirOk) return [...files];
|
|
160
|
-
return files.filter((f) => !
|
|
254
|
+
return files.filter((f) => !fs3.existsSync(path3.join(dir, f)));
|
|
161
255
|
}
|
|
162
256
|
function installBundledPayload(opts) {
|
|
163
257
|
const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
|
|
@@ -165,8 +259,8 @@ function installBundledPayload(opts) {
|
|
|
165
259
|
}
|
|
166
260
|
|
|
167
261
|
// src/wizard/service.ts
|
|
168
|
-
import
|
|
169
|
-
import
|
|
262
|
+
import fs4 from "fs";
|
|
263
|
+
import path4 from "path";
|
|
170
264
|
function systemdUnit(spec) {
|
|
171
265
|
return [
|
|
172
266
|
"[Unit]",
|
|
@@ -213,7 +307,7 @@ function launchdPlist(spec, label) {
|
|
|
213
307
|
}
|
|
214
308
|
function planService(platform, spec) {
|
|
215
309
|
if (platform === "linux") {
|
|
216
|
-
const unitPath =
|
|
310
|
+
const unitPath = path4.join(
|
|
217
311
|
spec.homeDir,
|
|
218
312
|
".config",
|
|
219
313
|
"systemd",
|
|
@@ -230,7 +324,7 @@ function planService(platform, spec) {
|
|
|
230
324
|
}
|
|
231
325
|
if (platform === "darwin") {
|
|
232
326
|
const label = darwinLabel(spec.serviceName);
|
|
233
|
-
const unitPath =
|
|
327
|
+
const unitPath = path4.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
|
|
234
328
|
return {
|
|
235
329
|
supported: true,
|
|
236
330
|
unitPath,
|
|
@@ -250,23 +344,23 @@ function planService(platform, spec) {
|
|
|
250
344
|
function writeService(platform, spec) {
|
|
251
345
|
const plan = planService(platform, spec);
|
|
252
346
|
if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
|
|
253
|
-
|
|
254
|
-
|
|
347
|
+
fs4.mkdirSync(path4.dirname(plan.unitPath), { recursive: true });
|
|
348
|
+
fs4.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
|
|
255
349
|
}
|
|
256
350
|
return plan;
|
|
257
351
|
}
|
|
258
352
|
|
|
259
353
|
// src/wizard/installers/hermes.ts
|
|
260
354
|
function installHermes(inp) {
|
|
261
|
-
const daemonEnv =
|
|
262
|
-
const adapterEnv =
|
|
263
|
-
const pluginDir =
|
|
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");
|
|
264
358
|
if (!inp.skipPayload) {
|
|
265
359
|
const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
|
|
266
360
|
if (missing.length > 0) {
|
|
267
361
|
return {
|
|
268
362
|
runtime: "hermes",
|
|
269
|
-
output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${
|
|
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.`,
|
|
270
364
|
exitCode: 2,
|
|
271
365
|
secret: "",
|
|
272
366
|
secretReused: false
|
|
@@ -325,66 +419,49 @@ function installHermes(inp) {
|
|
|
325
419
|
|
|
326
420
|
// src/wizard/installers/openclaw.ts
|
|
327
421
|
function installOpenclaw(inp) {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// Token-mode → credential lands here (config-first). Paired-mode → omitted.
|
|
333
|
-
...inp.token ? { credential: inp.token } : {},
|
|
334
|
-
tandems: [],
|
|
335
|
-
selfPrincipals: [],
|
|
336
|
-
wake: { severities: [], mentionsOnly: false }
|
|
337
|
-
};
|
|
338
|
-
const stamp = (inp.now ? inp.now() : /* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
339
|
-
const { backedUp } = writeOpenclawChannelConfig(inp.openclawConfigPath, block, { timestamp: stamp });
|
|
340
|
-
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
|
+
});
|
|
341
426
|
const lines = [];
|
|
342
|
-
if (backedUp) {
|
|
427
|
+
if (r.backedUp) {
|
|
343
428
|
lines.push(
|
|
344
|
-
`WARNING: ${inp.openclawConfigPath} was present but
|
|
345
|
-
` The original was backed up to ${backedUp} before writing the kojee-tandem block.`,
|
|
346
|
-
` 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.`,
|
|
347
430
|
""
|
|
348
431
|
);
|
|
349
432
|
}
|
|
350
433
|
lines.push(
|
|
351
|
-
"Configured runtime: openclaw (
|
|
352
|
-
"Wake mode:
|
|
353
|
-
|
|
354
|
-
|
|
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"}`,
|
|
355
440
|
"",
|
|
356
441
|
"Next steps (openclaw):",
|
|
357
|
-
|
|
358
|
-
"
|
|
359
|
-
" -
|
|
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"
|
|
360
446
|
);
|
|
361
|
-
return {
|
|
362
|
-
runtime: "openclaw",
|
|
363
|
-
output: lines.join("\n"),
|
|
364
|
-
exitCode: 0,
|
|
365
|
-
configPath: inp.openclawConfigPath
|
|
366
|
-
};
|
|
447
|
+
return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
|
|
367
448
|
}
|
|
368
449
|
function uninstallOpenclaw(inp) {
|
|
369
|
-
const removed =
|
|
370
|
-
const lines = [
|
|
371
|
-
|
|
372
|
-
removed ? ` removed
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
runtime: "openclaw",
|
|
380
|
-
output: lines.join("\n"),
|
|
381
|
-
exitCode: 0,
|
|
382
|
-
configPath: inp.openclawConfigPath
|
|
383
|
-
};
|
|
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 };
|
|
384
460
|
}
|
|
385
461
|
|
|
386
462
|
// src/wizard/wizard.ts
|
|
387
463
|
var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
|
|
464
|
+
var HERMES_DEFAULT_WEBHOOK_URL = "http://127.0.0.1:8645/kojee-tandem";
|
|
388
465
|
function generateWebhookSecret() {
|
|
389
466
|
return crypto2.randomBytes(32).toString("hex");
|
|
390
467
|
}
|
|
@@ -469,15 +546,25 @@ async function gatherGuidedInputs(runtime, opts) {
|
|
|
469
546
|
} else {
|
|
470
547
|
const code = opts.promptPairCode ? (await opts.promptPairCode()).trim() : "";
|
|
471
548
|
if (!code) return { error: "No pair code entered. Re-run `kojee-mcp init` and enter a pair code, or choose token mode." };
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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);
|
|
477
556
|
});
|
|
478
557
|
try {
|
|
479
|
-
const { message } = await pair({
|
|
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
|
+
});
|
|
480
566
|
preamble.push(`Auth: pair mode \u2014 ${message}`);
|
|
567
|
+
if (slot.pairedConfigPath) next.pairedConfigPath = slot.pairedConfigPath;
|
|
481
568
|
} catch (err) {
|
|
482
569
|
return { error: `Pairing failed: ${err.message}` };
|
|
483
570
|
}
|
|
@@ -524,6 +611,8 @@ function whatHappensNext(runtime, opts) {
|
|
|
524
611
|
lines.push(" - Restart your runtime so it picks up the new kojee config.");
|
|
525
612
|
if (opts.token) {
|
|
526
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}).`);
|
|
527
616
|
} else {
|
|
528
617
|
lines.push(" - The proxy uses your paired credentials (~/.kojee/config.json + keypair.json).");
|
|
529
618
|
}
|
|
@@ -531,13 +620,18 @@ function whatHappensNext(runtime, opts) {
|
|
|
531
620
|
return lines.join("\n");
|
|
532
621
|
}
|
|
533
622
|
async function configureClaudeCode(opts) {
|
|
534
|
-
const { runInit } = await import("./install-
|
|
623
|
+
const { runInit } = await import("./install-V7LSQCYZ.js");
|
|
535
624
|
const report = runInit({
|
|
536
625
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
537
626
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
538
627
|
// Token mode threads --token/--url into the written args (per-token
|
|
539
628
|
// keystore). Paired mode leaves both unset ⇒ args stay `["kojee-mcp"]`.
|
|
540
|
-
...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 } : {}
|
|
541
635
|
});
|
|
542
636
|
recordRuntime("claude-code");
|
|
543
637
|
return { runtime: "claude-code", output: formatClaudeInit(report), exitCode: 0 };
|
|
@@ -546,6 +640,7 @@ function formatClaudeInit(report) {
|
|
|
546
640
|
const tick = (s) => {
|
|
547
641
|
if (s === "added") return "\u2713 added";
|
|
548
642
|
if (s === "already-installed") return "\u21BB already installed";
|
|
643
|
+
if (s === "updated") return "\u21BB updated";
|
|
549
644
|
if (s === "preserved-different") return "\u26A0 preserved (existing entry differs \u2014 left untouched)";
|
|
550
645
|
if (s === "not-found") return "\u2014 not found";
|
|
551
646
|
return s ?? "\u2014";
|
|
@@ -583,44 +678,60 @@ function configureCodex(opts) {
|
|
|
583
678
|
if (wh.error) {
|
|
584
679
|
return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
585
680
|
}
|
|
586
|
-
const
|
|
587
|
-
const
|
|
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();
|
|
588
685
|
const tokenArgs = opts.token && opts.url ? { token: opts.token, url: opts.url } : {};
|
|
686
|
+
const pairedArgs = opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {};
|
|
589
687
|
writeCodexConfig({
|
|
590
688
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
591
689
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
592
|
-
webhookUrl: url,
|
|
593
|
-
webhookSecret: secret,
|
|
690
|
+
...url !== void 0 ? { webhookUrl: url } : {},
|
|
691
|
+
...secret !== void 0 ? { webhookSecret: secret } : {},
|
|
594
692
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
595
|
-
...tokenArgs
|
|
693
|
+
...tokenArgs,
|
|
694
|
+
...pairedArgs
|
|
596
695
|
});
|
|
597
696
|
recordRuntime("codex");
|
|
598
697
|
const lines = [];
|
|
599
698
|
lines.push("Configured runtime: codex");
|
|
600
|
-
lines.push(
|
|
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
|
+
);
|
|
601
702
|
lines.push("");
|
|
602
703
|
lines.push("Wrote [mcp_servers.kojee] to ~/.codex/config.toml:");
|
|
603
704
|
lines.push(indent(buildCodexMcpServerTable({
|
|
604
|
-
|
|
605
|
-
|
|
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>" } : {},
|
|
606
709
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
607
710
|
// Redact the gateway token in the human-readable printed copy (it ends up
|
|
608
711
|
// in result.output → cli.ts console.error). The WRITTEN config above keeps
|
|
609
712
|
// the real token; only this printed report is redacted, like webhookSecret.
|
|
610
|
-
...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
|
|
611
716
|
})));
|
|
612
717
|
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
613
718
|
lines.push("");
|
|
614
719
|
lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
|
|
615
720
|
lines.push(indent(buildCodexStopHookBlock()));
|
|
616
721
|
lines.push("");
|
|
617
|
-
lines.push(
|
|
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
|
+
);
|
|
618
725
|
lines.push(`bounded-listen cap: ${CODEX_LISTEN_CAP_MS}ms (the model picks listen vs drain vs ignore).`);
|
|
619
726
|
lines.push("");
|
|
620
727
|
lines.push("Next steps (codex):");
|
|
621
728
|
lines.push(" Restart Codex (or start a new `codex` / `codex exec` session) to load the MCP server and hook.");
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
}
|
|
624
735
|
lines.push(" Verify: kojee-mcp doctor");
|
|
625
736
|
lines.push("");
|
|
626
737
|
lines.push(CODEX_UNVERIFIED_NOTE);
|
|
@@ -649,7 +760,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
|
|
|
649
760
|
return lines;
|
|
650
761
|
}
|
|
651
762
|
function distDir() {
|
|
652
|
-
return
|
|
763
|
+
return path6.dirname(fileURLToPath(import.meta.url));
|
|
653
764
|
}
|
|
654
765
|
function resolveBinPath() {
|
|
655
766
|
const entry = process.argv[1];
|
|
@@ -657,14 +768,30 @@ function resolveBinPath() {
|
|
|
657
768
|
}
|
|
658
769
|
function configureHermes(opts) {
|
|
659
770
|
const runtime = "hermes";
|
|
660
|
-
const
|
|
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);
|
|
661
775
|
if (wh.error) {
|
|
662
776
|
return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
663
777
|
}
|
|
778
|
+
opts = effectiveOpts;
|
|
664
779
|
const lines = [];
|
|
665
780
|
lines.push(`Configured runtime: ${runtime}`);
|
|
666
781
|
lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
|
|
667
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
|
+
}
|
|
668
795
|
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
669
796
|
if (wh.url) {
|
|
670
797
|
const home = kojeeHomeDir();
|
|
@@ -694,7 +821,7 @@ function configureHermes(opts) {
|
|
|
694
821
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
695
822
|
}
|
|
696
823
|
recordRuntime(runtime);
|
|
697
|
-
const envFile =
|
|
824
|
+
const envFile = path6.join(home, ".kojee", "hermes.env");
|
|
698
825
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
699
826
|
lines.push("");
|
|
700
827
|
lines.push(install.output);
|
|
@@ -710,30 +837,13 @@ function configureHermes(opts) {
|
|
|
710
837
|
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
711
838
|
}
|
|
712
839
|
function openclawConfigPath(opts) {
|
|
713
|
-
return opts.openclawConfigPath ??
|
|
714
|
-
}
|
|
715
|
-
function resolveOpenclawPluginSourceDir() {
|
|
716
|
-
const candidates = [
|
|
717
|
-
path5.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
|
|
718
|
-
path5.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
|
|
719
|
-
];
|
|
720
|
-
for (const dir of candidates) {
|
|
721
|
-
if (fs4.existsSync(path5.join(dir, "openclaw.plugin.json"))) return dir;
|
|
722
|
-
}
|
|
723
|
-
return void 0;
|
|
840
|
+
return opts.openclawConfigPath ?? path6.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
|
|
724
841
|
}
|
|
725
842
|
function configureOpenclaw(opts) {
|
|
726
843
|
const runtime = "openclaw";
|
|
727
|
-
const env = opts.env ?? process.env;
|
|
728
|
-
const token = (opts.token ?? "").trim();
|
|
729
|
-
const url = (opts.url ?? env["KOJEE_GATEWAY_URL"] ?? "").trim();
|
|
730
|
-
const pluginSourceDir = resolveOpenclawPluginSourceDir();
|
|
731
844
|
const install = installOpenclaw({
|
|
732
|
-
homeDir: kojeeHomeDir(),
|
|
733
845
|
openclawConfigPath: openclawConfigPath(opts),
|
|
734
|
-
...
|
|
735
|
-
...token ? { token } : {},
|
|
736
|
-
...pluginSourceDir ? { pluginSourceDir } : {}
|
|
846
|
+
...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {}
|
|
737
847
|
});
|
|
738
848
|
recordRuntime(runtime);
|
|
739
849
|
return { runtime, output: install.output, exitCode: install.exitCode };
|
|
@@ -742,7 +852,7 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
742
852
|
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
743
853
|
const lines = [`Uninstalling runtime: ${effective}`];
|
|
744
854
|
if (effective === "claude-code") {
|
|
745
|
-
const { runUninstall } = await import("./install-
|
|
855
|
+
const { runUninstall } = await import("./install-V7LSQCYZ.js");
|
|
746
856
|
const report = runUninstall({
|
|
747
857
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
748
858
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
@@ -771,9 +881,9 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
771
881
|
} else {
|
|
772
882
|
lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
|
|
773
883
|
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
774
|
-
const envPath =
|
|
884
|
+
const envPath = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
775
885
|
try {
|
|
776
|
-
|
|
886
|
+
fs5.unlinkSync(envPath);
|
|
777
887
|
lines.push(` removed ${envPath}`);
|
|
778
888
|
} catch {
|
|
779
889
|
}
|
|
@@ -821,14 +931,13 @@ registerBuiltinInstaller(
|
|
|
821
931
|
registerBuiltinInstaller(
|
|
822
932
|
"openclaw",
|
|
823
933
|
"OpenClaw",
|
|
824
|
-
["pair-credential", "
|
|
934
|
+
["pair-credential", "mcp-config-write"],
|
|
825
935
|
(o) => configureOpenclaw(o)
|
|
826
936
|
);
|
|
937
|
+
|
|
827
938
|
export {
|
|
828
|
-
|
|
939
|
+
reconcileConnectPairSlot,
|
|
940
|
+
pairSlotFor,
|
|
829
941
|
DEFAULT_BROKER_URL,
|
|
830
|
-
generateWebhookSecret,
|
|
831
|
-
resolveRuntime,
|
|
832
|
-
resolveWizardWebhook,
|
|
833
942
|
runWizard
|
|
834
943
|
};
|