kojee-mcp 0.6.0 → 0.7.1
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-KH5YEJIM.js → chunk-247WFMCJ.js} +7 -20
- package/dist/chunk-5DHIUN73.js +25 -0
- 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-EBYUJM3H.js +14 -0
- package/dist/chunk-EIAUW6KO.js +82 -0
- package/dist/chunk-EIH2LNF4.js +45 -0
- package/dist/{chunk-BYV7CWNY.js → chunk-FBJCPRVH.js} +22 -9
- package/dist/{chunk-XN3IC4B5.js → chunk-FSKGQ6GT.js} +30 -5
- package/dist/{chunk-GI2CKKBL.js → chunk-GKXHKR3B.js} +1 -1
- package/dist/{chunk-ALZX3SGI.js → chunk-GNLCUJBK.js} +13 -27
- package/dist/{chunk-JXMVZEQ7.js → chunk-I67C2HYA.js} +1 -1
- package/dist/chunk-KNEJTD6G.js +31 -0
- package/dist/{chunk-65KRRDHP.js → chunk-KPMD72FY.js} +94 -28
- package/dist/{chunk-NR4Y54OL.js → chunk-MIEI4PLB.js} +1 -19
- package/dist/chunk-NE2F5CKS.js +113 -0
- package/dist/chunk-QJFMU4QC.js +33 -0
- package/dist/{chunk-EW72ZNQL.js → chunk-RDLF4NQC.js} +1 -1
- package/dist/{chunk-Q7T3WAST.js → chunk-SSW5AQSR.js} +6 -1
- package/dist/{chunk-DURRQDYI.js → chunk-TNZITJAB.js} +45 -15
- package/dist/chunk-U5HHHRXA.js +103 -0
- package/dist/{setup-handler-7HOFAD7C.js → chunk-UFHGZUST.js} +431 -253
- package/dist/{chunk-N35X22G7.js → chunk-UPJV7GBE.js} +1 -1
- package/dist/chunk-WBXU27BF.js +35 -0
- package/dist/chunk-XJEBJIQE.js +52 -0
- package/dist/chunk-Z5LPNJQ6.js +20 -0
- package/dist/cli.js +63 -25
- package/dist/codex-stop-hook-BMOJVM6O.js +96 -0
- package/dist/connect-handler-4DRTFMOB.js +138 -0
- package/dist/{control-token-4BUCTYQB.js → control-token-KXSZ3GBY.js} +2 -2
- package/dist/{doctor-WVX3WCYX.js → doctor-5QJ3HGNR.js} +48 -22
- package/dist/{doctor-codex-Z5M4SFBG.js → doctor-codex-VGJKTX2E.js} +33 -7
- package/dist/doctor-openclaw-LL6NSPLC.js +86 -0
- package/dist/{event-log-HOBO3ECB.js → event-log-2NBJEIEP.js} +5 -3
- package/dist/{event-stream-SYBUJ5EH.js → event-stream-KRYWEYWO.js} +7 -4
- package/dist/index.js +13 -10
- 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 +11 -8
- 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-YLOFZDI2.js → registry-ZZZ26WGA.js} +197 -24
- package/dist/resolve-slot-BFIB46Y5.js +13 -0
- package/dist/{runtime-record-WO4IECM6.js → runtime-record-OXRLTOLC.js} +2 -2
- package/dist/{send-cli-IYJF32PE.js → send-cli-45RLGYIC.js} +22 -10
- package/dist/{server-R24AVNTI.js → server-ITPFQVTK.js} +5 -2
- package/dist/{session-discovery-FNMJGFPM.js → session-discovery-NHCZ2XWA.js} +2 -2
- package/dist/setup-handler-JFM45NCN.js +160 -0
- package/dist/{stop-hook-TCPJFGFF.js → stop-hook-5ABGTC2O.js} +17 -20
- package/dist/{tail-stream-4IMXI44S.js → tail-stream-NBGHHBS4.js} +40 -22
- package/dist/{user-prompt-submit-hook-FQ43NIHC.js → user-prompt-submit-hook-DTXXFDSD.js} +11 -14
- package/package.json +3 -2
- package/skills/using-tandems/SKILL.md +52 -0
- package/dist/ancestry-ONFBQEP5.js +0 -8
- package/dist/chunk-BLEGIR35.js +0 -43
- package/dist/chunk-CLKCNV2A.js +0 -100
- package/dist/chunk-KFDZWNQO.js +0 -73
- package/dist/chunk-OGHDTFAX.js +0 -50
- package/dist/chunk-VHKPWUX7.js +0 -41
- package/dist/codex-stop-hook-RWQSZTST.js +0 -16
- package/dist/doctor-openclaw-SS2TMQOX.js +0 -95
- package/dist/pair-P4ILCMT7.js +0 -10
|
@@ -1,19 +1,22 @@
|
|
|
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,
|
|
8
|
+
isPlaceholderWebhookUrl,
|
|
9
9
|
removeCodexConfig,
|
|
10
10
|
writeCodexConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KPMD72FY.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
CONNECT_RUNTIMES
|
|
14
|
+
} from "./chunk-EIH2LNF4.js";
|
|
15
|
+
import {
|
|
16
|
+
clearRuntimeRecord,
|
|
17
|
+
readRecordedRuntime,
|
|
18
|
+
recordRuntime
|
|
19
|
+
} from "./chunk-RDLF4NQC.js";
|
|
17
20
|
import {
|
|
18
21
|
kojeeHomeDir
|
|
19
22
|
} from "./chunk-SQL56SEB.js";
|
|
@@ -26,21 +29,265 @@ import {
|
|
|
26
29
|
resolveWebhookConfig
|
|
27
30
|
} from "./chunk-V5VZPYMZ.js";
|
|
28
31
|
import {
|
|
29
|
-
loadPairedConfig
|
|
30
|
-
|
|
32
|
+
loadPairedConfig,
|
|
33
|
+
pairedConfigPath,
|
|
34
|
+
perRuntimeConfigPath
|
|
35
|
+
} from "./chunk-5SZHXYPK.js";
|
|
36
|
+
import {
|
|
37
|
+
defaultPairedKeystorePath,
|
|
38
|
+
deriveKeystorePath
|
|
39
|
+
} from "./chunk-6G6YYST6.js";
|
|
40
|
+
import {
|
|
41
|
+
secureFile,
|
|
42
|
+
unlinkSyncRetry
|
|
43
|
+
} from "./chunk-U5HHHRXA.js";
|
|
31
44
|
import {
|
|
32
|
-
|
|
33
|
-
} from "./chunk-
|
|
45
|
+
VERSION
|
|
46
|
+
} from "./chunk-5DHIUN73.js";
|
|
34
47
|
import {
|
|
35
48
|
CODEX_LISTEN_CAP_MS,
|
|
49
|
+
buildCondensedTandemRules,
|
|
36
50
|
buildWebhookReceiverNote
|
|
37
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-SSW5AQSR.js";
|
|
52
|
+
|
|
53
|
+
// src/wizard/pair-slot.ts
|
|
54
|
+
import fs from "fs";
|
|
55
|
+
import os from "os";
|
|
56
|
+
import path from "path";
|
|
57
|
+
function absPerRuntimeConfigPath(runtime) {
|
|
58
|
+
const p = path.resolve(perRuntimeConfigPath(runtime));
|
|
59
|
+
if (process.platform === "win32" && !/^[a-zA-Z]:/.test(p)) {
|
|
60
|
+
return path.resolve(path.join(os.homedir(), ".kojee", "agents", runtime, "config.json"));
|
|
61
|
+
}
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
function reconcileConnectPairSlot(runtime, code) {
|
|
65
|
+
const messages = [];
|
|
66
|
+
for (const other of CONNECT_RUNTIMES) {
|
|
67
|
+
if (other === runtime) continue;
|
|
68
|
+
const sibling = loadPairedConfig(perRuntimeConfigPath(other));
|
|
69
|
+
if (sibling?.token === code) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
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.`,
|
|
73
|
+
exitCode: 2
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!CONNECT_RUNTIMES.includes(runtime)) {
|
|
78
|
+
return { ok: true, messages };
|
|
79
|
+
}
|
|
80
|
+
const legacy = loadPairedConfig(pairedConfigPath());
|
|
81
|
+
if (legacy && legacy.token === code) {
|
|
82
|
+
if (legacy.runtime === "hermes") {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
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.`,
|
|
86
|
+
exitCode: 2
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
unlinkSyncRetry(pairedConfigPath());
|
|
91
|
+
} catch (err) {
|
|
92
|
+
const e = err;
|
|
93
|
+
if (e.code !== "ENOENT") {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
output: `Aborted: could not retire the legacy global config at ${pairedConfigPath()} (${e.message}). Close any running kojee proxy and retry.`,
|
|
97
|
+
exitCode: 1
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const legacyKeystore = defaultPairedKeystorePath();
|
|
102
|
+
const target = deriveKeystorePath(code);
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(legacyKeystore) && !fs.existsSync(target)) {
|
|
105
|
+
fs.renameSync(legacyKeystore, target);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
messages.push("Migrated the legacy global slot into this per-runtime connection.");
|
|
110
|
+
}
|
|
111
|
+
return { ok: true, messages };
|
|
112
|
+
}
|
|
113
|
+
function pairSlotFor(runtime, code) {
|
|
114
|
+
if (CONNECT_RUNTIMES.includes(runtime)) {
|
|
115
|
+
const configPath = absPerRuntimeConfigPath(runtime);
|
|
116
|
+
return {
|
|
117
|
+
configPath,
|
|
118
|
+
keystorePath: deriveKeystorePath(code),
|
|
119
|
+
runtime,
|
|
120
|
+
allowOverwrite: true,
|
|
121
|
+
pairedConfigPath: configPath
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
configPath: pairedConfigPath(),
|
|
126
|
+
keystorePath: defaultPairedKeystorePath(),
|
|
127
|
+
// F2: stamp the pairing runtime into the global PairedConfig so a later
|
|
128
|
+
// connect-runtime reconcile can recognize a live hermes slot and refuse to
|
|
129
|
+
// retire it (see reconcileConnectPairSlot).
|
|
130
|
+
runtime,
|
|
131
|
+
allowOverwrite: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
38
134
|
|
|
39
135
|
// src/wizard/wizard.ts
|
|
40
|
-
import
|
|
41
|
-
import
|
|
42
|
-
import
|
|
136
|
+
import crypto3 from "crypto";
|
|
137
|
+
import fs6 from "fs";
|
|
138
|
+
import path7 from "path";
|
|
139
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
140
|
+
|
|
141
|
+
// src/wizard/skill-install.ts
|
|
142
|
+
import crypto from "crypto";
|
|
143
|
+
import fs2 from "fs";
|
|
144
|
+
import path2 from "path";
|
|
43
145
|
import { fileURLToPath } from "url";
|
|
146
|
+
var SKILL_NAME = "using-tandems";
|
|
147
|
+
var SKILL_MARKER_PREFIX = "kojee-mcp:skill using-tandems";
|
|
148
|
+
function hashSkillBody(body) {
|
|
149
|
+
return crypto.createHash("sha256").update(body, "utf8").digest("hex").slice(0, 12);
|
|
150
|
+
}
|
|
151
|
+
function bundledSkillSourcePath() {
|
|
152
|
+
const here = path2.dirname(fileURLToPath(import.meta.url));
|
|
153
|
+
const candidates = [
|
|
154
|
+
path2.join(here, "..", "skills", SKILL_NAME, "SKILL.md"),
|
|
155
|
+
path2.join(here, "..", "..", "skills", SKILL_NAME, "SKILL.md")
|
|
156
|
+
];
|
|
157
|
+
for (const c of candidates) {
|
|
158
|
+
if (fs2.existsSync(c)) return c;
|
|
159
|
+
}
|
|
160
|
+
throw new Error(
|
|
161
|
+
`bundled skill '${SKILL_NAME}' not found (looked at: ${candidates.join(", ")}) \u2014 the package is missing its skills/ payload; reinstall kojee-mcp.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function readBundledSkill() {
|
|
165
|
+
const body = fs2.readFileSync(bundledSkillSourcePath(), "utf8").replace(/\s+$/, "") + "\n";
|
|
166
|
+
return { body, version: VERSION, hash: hashSkillBody(body) };
|
|
167
|
+
}
|
|
168
|
+
function markerLine(version, hash) {
|
|
169
|
+
return `<!-- ${SKILL_MARKER_PREFIX} v${version} sha256:${hash} \u2014 managed by kojee-mcp; your edits are preserved (a modified file is never auto-updated or auto-removed) -->`;
|
|
170
|
+
}
|
|
171
|
+
function renderInstalledSkill(b) {
|
|
172
|
+
return `${b.body}
|
|
173
|
+
${markerLine(b.version, hashSkillBody(b.body))}
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
var MARKER_RE = new RegExp(`<!-- ${SKILL_MARKER_PREFIX} v(\\S+) sha256:([0-9a-f]{12})[^>]*-->`);
|
|
177
|
+
function parseInstalledSkill(content) {
|
|
178
|
+
const m = MARKER_RE.exec(content);
|
|
179
|
+
if (!m) return null;
|
|
180
|
+
const before = content.slice(0, m.index);
|
|
181
|
+
const after = content.slice(m.index + m[0].length);
|
|
182
|
+
const body = (before + after).replace(/\s+$/, "") + "\n";
|
|
183
|
+
return { version: m[1], hash: m[2], body };
|
|
184
|
+
}
|
|
185
|
+
function compareVersions(a, b) {
|
|
186
|
+
const parse = (v) => (v.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
187
|
+
const x = parse(a);
|
|
188
|
+
const y = parse(b);
|
|
189
|
+
for (let i = 0; i < 3; i++) {
|
|
190
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
191
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
192
|
+
}
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
function readDest(dest) {
|
|
196
|
+
try {
|
|
197
|
+
return { existing: fs2.readFileSync(dest, "utf8"), unreadable: false };
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (err.code === "ENOENT") return { existing: null, unreadable: false };
|
|
200
|
+
return { existing: null, unreadable: true };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function installSkillFile(skillsRoot, bundled = readBundledSkill()) {
|
|
204
|
+
const dir = path2.join(skillsRoot, SKILL_NAME);
|
|
205
|
+
const dest = path2.join(dir, "SKILL.md");
|
|
206
|
+
const { existing, unreadable } = readDest(dest);
|
|
207
|
+
if (unreadable) return { status: "skipped-unreadable", path: dest };
|
|
208
|
+
if (existing === null) {
|
|
209
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
210
|
+
fs2.writeFileSync(dest, renderInstalledSkill(bundled));
|
|
211
|
+
return { status: "installed", path: dest };
|
|
212
|
+
}
|
|
213
|
+
const parsed = parseInstalledSkill(existing);
|
|
214
|
+
if (!parsed) return { status: "skipped-unmanaged", path: dest };
|
|
215
|
+
if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
|
|
216
|
+
const cmp = compareVersions(bundled.version, parsed.version);
|
|
217
|
+
if (cmp < 0) return { status: "kept-newer", path: dest };
|
|
218
|
+
if (cmp === 0 && hashSkillBody(bundled.body) === parsed.hash)
|
|
219
|
+
return { status: "up-to-date", path: dest };
|
|
220
|
+
fs2.writeFileSync(dest, renderInstalledSkill(bundled));
|
|
221
|
+
return { status: "updated", path: dest };
|
|
222
|
+
}
|
|
223
|
+
function removeSkillFile(skillsRoot) {
|
|
224
|
+
const dir = path2.join(skillsRoot, SKILL_NAME);
|
|
225
|
+
const dest = path2.join(dir, "SKILL.md");
|
|
226
|
+
const { existing, unreadable } = readDest(dest);
|
|
227
|
+
if (unreadable) return { status: "skipped-unreadable", path: dest };
|
|
228
|
+
if (existing === null) return { status: "not-found", path: dest };
|
|
229
|
+
const parsed = parseInstalledSkill(existing);
|
|
230
|
+
if (!parsed) return { status: "skipped-unmanaged", path: dest };
|
|
231
|
+
if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
|
|
232
|
+
fs2.unlinkSync(dest);
|
|
233
|
+
try {
|
|
234
|
+
fs2.rmdirSync(dir);
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
return { status: "removed", path: dest };
|
|
238
|
+
}
|
|
239
|
+
function skillsRootFor(runtime, homeDir = kojeeHomeDir()) {
|
|
240
|
+
if (runtime === "claude-code") return path2.join(homeDir, ".claude", "skills");
|
|
241
|
+
if (runtime === "codex") return path2.join(homeDir, ".agents", "skills");
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
var LABEL = `skill (${SKILL_NAME})`;
|
|
245
|
+
function installSkillForRuntime(runtime, skillsRootOverride) {
|
|
246
|
+
const root = skillsRootOverride ?? skillsRootFor(runtime);
|
|
247
|
+
if (!root) return null;
|
|
248
|
+
try {
|
|
249
|
+
const r = installSkillFile(root);
|
|
250
|
+
switch (r.status) {
|
|
251
|
+
case "installed":
|
|
252
|
+
return `${LABEL}: \u2713 installed \u2192 ${r.path}`;
|
|
253
|
+
case "updated":
|
|
254
|
+
return `${LABEL}: \u21BB updated to v${VERSION} \u2192 ${r.path}`;
|
|
255
|
+
case "up-to-date":
|
|
256
|
+
return `${LABEL}: \u21BB up to date (v${VERSION})`;
|
|
257
|
+
case "kept-newer":
|
|
258
|
+
return `${LABEL}: \u2014 kept (installed copy is newer than this proxy's v${VERSION})`;
|
|
259
|
+
case "skipped-unmanaged":
|
|
260
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists without a kojee-mcp marker (not ours; left untouched)`;
|
|
261
|
+
case "skipped-modified":
|
|
262
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} was modified locally (edits preserved; delete the file to receive updates)`;
|
|
263
|
+
case "skipped-unreadable":
|
|
264
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists but could not be read (not verifying ownership \u2192 not touching it)`;
|
|
265
|
+
}
|
|
266
|
+
} catch (err) {
|
|
267
|
+
return `${LABEL}: WARNING \u2014 could not install (${err.message}); the runtime still works without it.`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function removeSkillForRuntime(runtime, skillsRootOverride) {
|
|
271
|
+
const root = skillsRootOverride ?? skillsRootFor(runtime);
|
|
272
|
+
if (!root) return null;
|
|
273
|
+
try {
|
|
274
|
+
const r = removeSkillFile(root);
|
|
275
|
+
switch (r.status) {
|
|
276
|
+
case "removed":
|
|
277
|
+
return `${LABEL}: \u2713 removed ${r.path}`;
|
|
278
|
+
case "not-found":
|
|
279
|
+
return `${LABEL}: \u2014 not found`;
|
|
280
|
+
case "skipped-unmanaged":
|
|
281
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} has no kojee-mcp marker (not ours)`;
|
|
282
|
+
case "skipped-modified":
|
|
283
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} was modified locally (never deleting your edits; remove it manually)`;
|
|
284
|
+
case "skipped-unreadable":
|
|
285
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} exists but could not be read (cannot verify it's ours)`;
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
return `${LABEL}: WARNING \u2014 could not remove (${err.message}).`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
44
291
|
|
|
45
292
|
// src/wizard/registry.ts
|
|
46
293
|
var installers = /* @__PURE__ */ new Map();
|
|
@@ -55,16 +302,16 @@ function runtimeUsesWebhook(id) {
|
|
|
55
302
|
}
|
|
56
303
|
|
|
57
304
|
// src/wizard/capabilities/webhook-secret.ts
|
|
58
|
-
import
|
|
59
|
-
import
|
|
60
|
-
import
|
|
305
|
+
import crypto2 from "crypto";
|
|
306
|
+
import fs3 from "fs";
|
|
307
|
+
import path3 from "path";
|
|
61
308
|
function generateSecret() {
|
|
62
|
-
return
|
|
309
|
+
return crypto2.randomBytes(32).toString("hex");
|
|
63
310
|
}
|
|
64
311
|
function readSecretFromEnv(envPath) {
|
|
65
312
|
let body;
|
|
66
313
|
try {
|
|
67
|
-
body =
|
|
314
|
+
body = fs3.readFileSync(envPath, "utf8");
|
|
68
315
|
} catch {
|
|
69
316
|
return void 0;
|
|
70
317
|
}
|
|
@@ -77,10 +324,10 @@ function shellSingleQuote(value) {
|
|
|
77
324
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
78
325
|
}
|
|
79
326
|
function upsertEnvFile(filePath, vars, opts = {}) {
|
|
80
|
-
|
|
327
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
81
328
|
let lines = [];
|
|
82
329
|
try {
|
|
83
|
-
lines =
|
|
330
|
+
lines = fs3.readFileSync(filePath, "utf8").split("\n");
|
|
84
331
|
} catch {
|
|
85
332
|
}
|
|
86
333
|
if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
|
|
@@ -92,7 +339,7 @@ function upsertEnvFile(filePath, vars, opts = {}) {
|
|
|
92
339
|
if (idx >= 0) lines[idx] = line;
|
|
93
340
|
else lines.push(line);
|
|
94
341
|
}
|
|
95
|
-
|
|
342
|
+
fs3.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
|
|
96
343
|
}
|
|
97
344
|
function writeWebhookSecretBothSides(opts) {
|
|
98
345
|
const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
|
|
@@ -120,11 +367,11 @@ function writeWebhookSecretBothSides(opts) {
|
|
|
120
367
|
}
|
|
121
368
|
|
|
122
369
|
// src/wizard/installers/hermes.ts
|
|
123
|
-
import
|
|
370
|
+
import path6 from "path";
|
|
124
371
|
|
|
125
372
|
// src/wizard/plugin-payload.ts
|
|
126
|
-
import
|
|
127
|
-
import
|
|
373
|
+
import fs4 from "fs";
|
|
374
|
+
import path4 from "path";
|
|
128
375
|
var HERMES_PAYLOAD_FILES = [
|
|
129
376
|
"__init__.py",
|
|
130
377
|
"adapter.py",
|
|
@@ -132,24 +379,24 @@ var HERMES_PAYLOAD_FILES = [
|
|
|
132
379
|
"plugin.yaml"
|
|
133
380
|
];
|
|
134
381
|
function stagePluginPayload(opts) {
|
|
135
|
-
|
|
382
|
+
fs4.mkdirSync(opts.destDir, { recursive: true });
|
|
136
383
|
const written = [];
|
|
137
384
|
for (const file of opts.files) {
|
|
138
|
-
const src =
|
|
139
|
-
if (!
|
|
385
|
+
const src = path4.join(opts.srcDir, file);
|
|
386
|
+
if (!fs4.existsSync(src)) {
|
|
140
387
|
throw new Error(
|
|
141
388
|
`stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
|
|
142
389
|
);
|
|
143
390
|
}
|
|
144
|
-
const dest =
|
|
145
|
-
|
|
391
|
+
const dest = path4.join(opts.destDir, file);
|
|
392
|
+
fs4.copyFileSync(src, dest);
|
|
146
393
|
written.push(dest);
|
|
147
394
|
}
|
|
148
395
|
return written;
|
|
149
396
|
}
|
|
150
397
|
function resolveBundledPayloadDir(runtime, baseDir) {
|
|
151
|
-
const dir =
|
|
152
|
-
if (!
|
|
398
|
+
const dir = path4.join(baseDir, "plugins", runtime);
|
|
399
|
+
if (!fs4.existsSync(dir) || !fs4.statSync(dir).isDirectory()) {
|
|
153
400
|
throw new Error(
|
|
154
401
|
`resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
|
|
155
402
|
);
|
|
@@ -157,10 +404,10 @@ function resolveBundledPayloadDir(runtime, baseDir) {
|
|
|
157
404
|
return dir;
|
|
158
405
|
}
|
|
159
406
|
function missingPayloadFiles(runtime, baseDir, files) {
|
|
160
|
-
const dir =
|
|
161
|
-
const dirOk =
|
|
407
|
+
const dir = path4.join(baseDir, "plugins", runtime);
|
|
408
|
+
const dirOk = fs4.existsSync(dir) && fs4.statSync(dir).isDirectory();
|
|
162
409
|
if (!dirOk) return [...files];
|
|
163
|
-
return files.filter((f) => !
|
|
410
|
+
return files.filter((f) => !fs4.existsSync(path4.join(dir, f)));
|
|
164
411
|
}
|
|
165
412
|
function installBundledPayload(opts) {
|
|
166
413
|
const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
|
|
@@ -168,8 +415,8 @@ function installBundledPayload(opts) {
|
|
|
168
415
|
}
|
|
169
416
|
|
|
170
417
|
// src/wizard/service.ts
|
|
171
|
-
import
|
|
172
|
-
import
|
|
418
|
+
import fs5 from "fs";
|
|
419
|
+
import path5 from "path";
|
|
173
420
|
function systemdUnit(spec) {
|
|
174
421
|
return [
|
|
175
422
|
"[Unit]",
|
|
@@ -216,7 +463,7 @@ function launchdPlist(spec, label) {
|
|
|
216
463
|
}
|
|
217
464
|
function planService(platform, spec) {
|
|
218
465
|
if (platform === "linux") {
|
|
219
|
-
const unitPath =
|
|
466
|
+
const unitPath = path5.join(
|
|
220
467
|
spec.homeDir,
|
|
221
468
|
".config",
|
|
222
469
|
"systemd",
|
|
@@ -233,7 +480,7 @@ function planService(platform, spec) {
|
|
|
233
480
|
}
|
|
234
481
|
if (platform === "darwin") {
|
|
235
482
|
const label = darwinLabel(spec.serviceName);
|
|
236
|
-
const unitPath =
|
|
483
|
+
const unitPath = path5.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
|
|
237
484
|
return {
|
|
238
485
|
supported: true,
|
|
239
486
|
unitPath,
|
|
@@ -253,23 +500,23 @@ function planService(platform, spec) {
|
|
|
253
500
|
function writeService(platform, spec) {
|
|
254
501
|
const plan = planService(platform, spec);
|
|
255
502
|
if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
|
|
256
|
-
|
|
257
|
-
|
|
503
|
+
fs5.mkdirSync(path5.dirname(plan.unitPath), { recursive: true });
|
|
504
|
+
fs5.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
|
|
258
505
|
}
|
|
259
506
|
return plan;
|
|
260
507
|
}
|
|
261
508
|
|
|
262
509
|
// src/wizard/installers/hermes.ts
|
|
263
510
|
function installHermes(inp) {
|
|
264
|
-
const daemonEnv =
|
|
265
|
-
const adapterEnv =
|
|
266
|
-
const pluginDir =
|
|
511
|
+
const daemonEnv = path6.join(inp.homeDir, ".kojee", "hermes.env");
|
|
512
|
+
const adapterEnv = path6.join(inp.homeDir, ".hermes", ".env");
|
|
513
|
+
const pluginDir = path6.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
|
|
267
514
|
if (!inp.skipPayload) {
|
|
268
515
|
const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
|
|
269
516
|
if (missing.length > 0) {
|
|
270
517
|
return {
|
|
271
518
|
runtime: "hermes",
|
|
272
|
-
output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${
|
|
519
|
+
output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${path6.join(inp.payloadBaseDir, "plugins", "hermes")}. No changes were written. Run \`npm run build\` to stage dist/plugins/hermes, then retry.`,
|
|
273
520
|
exitCode: 2,
|
|
274
521
|
secret: "",
|
|
275
522
|
secretReused: false
|
|
@@ -328,68 +575,59 @@ function installHermes(inp) {
|
|
|
328
575
|
|
|
329
576
|
// src/wizard/installers/openclaw.ts
|
|
330
577
|
function installOpenclaw(inp) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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)`;
|
|
578
|
+
const r = writeOpenclawMcpConfig(inp.openclawConfigPath, {
|
|
579
|
+
...inp.pairedConfigPath ? { pairedConfigPath: inp.pairedConfigPath } : {},
|
|
580
|
+
...inp.now ? { now: inp.now } : {}
|
|
581
|
+
});
|
|
344
582
|
const lines = [];
|
|
345
|
-
if (backedUp) {
|
|
583
|
+
if (r.backedUp) {
|
|
346
584
|
lines.push(
|
|
347
|
-
`WARNING: ${inp.openclawConfigPath} was present but
|
|
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.`,
|
|
585
|
+
`WARNING: ${inp.openclawConfigPath} was present but unparseable; backed up to ${r.backedUp} before writing.`,
|
|
350
586
|
""
|
|
351
587
|
);
|
|
352
588
|
}
|
|
353
589
|
lines.push(
|
|
354
|
-
"Configured runtime: openclaw (
|
|
355
|
-
"Wake mode:
|
|
356
|
-
|
|
357
|
-
|
|
590
|
+
"Configured runtime: openclaw (MCP server + /wake injector)",
|
|
591
|
+
"Wake mode: kojee-mcp runs as OpenClaw's MCP server; Tandem events POST to the local",
|
|
592
|
+
" OpenClaw gateway /hooks/wake (Bearer hooks.token) \u2192 the agent wakes.",
|
|
593
|
+
` mcp config: ${inp.openclawConfigPath} (mcp.servers.kojee)`,
|
|
594
|
+
inp.pairedConfigPath ? ` credentials: per-runtime paired slot (${inp.pairedConfigPath})` : ` credentials: shared ~/.kojee/config.json (paired)`,
|
|
595
|
+
` hooks token: ${r.hooksTokenGenerated ? "generated + hooks.enabled=true (new)" : "preserved existing"}`,
|
|
358
596
|
"",
|
|
359
597
|
"Next steps (openclaw):",
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
" -
|
|
598
|
+
" - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
|
|
599
|
+
" wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
|
|
600
|
+
" - Restart the OpenClaw gateway to spawn the kojee MCP server: openclaw gateway restart",
|
|
601
|
+
" - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
|
|
602
|
+
"",
|
|
603
|
+
// Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
|
|
604
|
+
// `instructions` field (buildNonChannelInstructions), but not every MCP host
|
|
605
|
+
// surfaces server instructions to the agent — print them here too so the
|
|
606
|
+
// operator can paste them into the agent's own instructions if needed.
|
|
607
|
+
"Agent ground rules (also delivered via the kojee MCP server's instructions;",
|
|
608
|
+
"add to your OpenClaw agent's instructions if it doesn't surface those):",
|
|
609
|
+
...buildCondensedTandemRules().split("\n").map((l) => ` ${l}`)
|
|
363
610
|
);
|
|
364
|
-
return {
|
|
365
|
-
runtime: "openclaw",
|
|
366
|
-
output: lines.join("\n"),
|
|
367
|
-
exitCode: 0,
|
|
368
|
-
configPath: inp.openclawConfigPath
|
|
369
|
-
};
|
|
611
|
+
return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
|
|
370
612
|
}
|
|
371
613
|
function uninstallOpenclaw(inp) {
|
|
372
|
-
const removed =
|
|
373
|
-
const lines = [
|
|
374
|
-
|
|
375
|
-
removed ? ` removed
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return {
|
|
382
|
-
runtime: "openclaw",
|
|
383
|
-
output: lines.join("\n"),
|
|
384
|
-
exitCode: 0,
|
|
385
|
-
configPath: inp.openclawConfigPath
|
|
386
|
-
};
|
|
614
|
+
const removed = removeOpenclawMcpServer(inp.openclawConfigPath);
|
|
615
|
+
const lines = [
|
|
616
|
+
"Uninstalling runtime: openclaw (MCP server + /wake injector)",
|
|
617
|
+
removed ? ` removed mcp.servers.kojee from ${inp.openclawConfigPath} (hooks + siblings preserved)` : ` no mcp.servers.kojee in ${inp.openclawConfigPath} \u2014 nothing to remove`,
|
|
618
|
+
"",
|
|
619
|
+
"Next steps (openclaw):",
|
|
620
|
+
" - Restart the OpenClaw gateway: openclaw gateway restart",
|
|
621
|
+
" - hooks.enabled/token were left intact (you may use hooks for other integrations)."
|
|
622
|
+
];
|
|
623
|
+
return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
|
|
387
624
|
}
|
|
388
625
|
|
|
389
626
|
// src/wizard/wizard.ts
|
|
390
627
|
var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
|
|
628
|
+
var HERMES_DEFAULT_WEBHOOK_URL = "http://127.0.0.1:8645/kojee-tandem";
|
|
391
629
|
function generateWebhookSecret() {
|
|
392
|
-
return
|
|
630
|
+
return crypto3.randomBytes(32).toString("hex");
|
|
393
631
|
}
|
|
394
632
|
async function resolveRuntime(opts) {
|
|
395
633
|
if (opts.runtime !== void 0) {
|
|
@@ -411,7 +649,8 @@ async function resolveRuntime(opts) {
|
|
|
411
649
|
}
|
|
412
650
|
function resolveWizardWebhook(opts) {
|
|
413
651
|
const env = opts.env ?? process.env;
|
|
414
|
-
const
|
|
652
|
+
const envUrl = (env["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
653
|
+
const url = (opts.webhookUrl ?? (isPlaceholderWebhookUrl(envUrl) ? "" : envUrl)).trim();
|
|
415
654
|
let secret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
416
655
|
if (url && !secret) secret = generateWebhookSecret();
|
|
417
656
|
const sigFormat = (opts.webhookSignatureFormat ?? env["KOJEE_WEBHOOK_SIGNATURE_FORMAT"] ?? "").trim();
|
|
@@ -472,15 +711,25 @@ async function gatherGuidedInputs(runtime, opts) {
|
|
|
472
711
|
} else {
|
|
473
712
|
const code = opts.promptPairCode ? (await opts.promptPairCode()).trim() : "";
|
|
474
713
|
if (!code) return { error: "No pair code entered. Re-run `kojee-mcp init` and enter a pair code, or choose token mode." };
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
714
|
+
const reconcile = reconcileConnectPairSlot(runtime, code);
|
|
715
|
+
if (!reconcile.ok) return { error: reconcile.output };
|
|
716
|
+
for (const m of reconcile.messages) preamble.push(m);
|
|
717
|
+
const slot = pairSlotFor(runtime, code);
|
|
718
|
+
const pair = opts.runPair ?? (async (o) => {
|
|
719
|
+
const { runPair } = await import("./pair-HTCVSMR6.js");
|
|
720
|
+
return runPair(o);
|
|
480
721
|
});
|
|
481
722
|
try {
|
|
482
|
-
const { message } = await pair({
|
|
723
|
+
const { message } = await pair({
|
|
724
|
+
code,
|
|
725
|
+
url: brokerUrl,
|
|
726
|
+
keystorePath: slot.keystorePath,
|
|
727
|
+
configPath: slot.configPath,
|
|
728
|
+
...slot.runtime ? { runtime: slot.runtime } : {},
|
|
729
|
+
...slot.allowOverwrite ? { allowOverwrite: true } : {}
|
|
730
|
+
});
|
|
483
731
|
preamble.push(`Auth: pair mode \u2014 ${message}`);
|
|
732
|
+
if (slot.pairedConfigPath) next.pairedConfigPath = slot.pairedConfigPath;
|
|
484
733
|
} catch (err) {
|
|
485
734
|
return { error: `Pairing failed: ${err.message}` };
|
|
486
735
|
}
|
|
@@ -513,20 +762,26 @@ async function runWizard(opts) {
|
|
|
513
762
|
}
|
|
514
763
|
const installer = getInstaller(runtime);
|
|
515
764
|
const result = installer ? await installer.install({ opts: effective }) : { runtime, output: `No installer registered for runtime '${runtime}'`, exitCode: 2 };
|
|
765
|
+
const skillLine = result.exitCode === 0 ? installSkillForRuntime(runtime, effective.skillsRoot) : null;
|
|
766
|
+
const body = skillLine ? `${result.output}
|
|
767
|
+
|
|
768
|
+
${skillLine}` : result.output;
|
|
516
769
|
if (preamble.length > 0 && result.exitCode === 0) {
|
|
517
770
|
return {
|
|
518
771
|
runtime,
|
|
519
|
-
output: [...preamble, "",
|
|
772
|
+
output: [...preamble, "", body, "", whatHappensNext(runtime, effective)].join("\n"),
|
|
520
773
|
exitCode: result.exitCode
|
|
521
774
|
};
|
|
522
775
|
}
|
|
523
|
-
return { runtime, output:
|
|
776
|
+
return { runtime, output: body, exitCode: result.exitCode };
|
|
524
777
|
}
|
|
525
778
|
function whatHappensNext(runtime, opts) {
|
|
526
779
|
const lines = ["What happens next:"];
|
|
527
780
|
lines.push(" - Restart your runtime so it picks up the new kojee config.");
|
|
528
781
|
if (opts.token) {
|
|
529
782
|
lines.push(" - First boot enrolls this token's own keystore (~/.kojee/keypair-<hash>.json).");
|
|
783
|
+
} else if (opts.pairedConfigPath) {
|
|
784
|
+
lines.push(` - The proxy uses your paired credentials (${opts.pairedConfigPath}).`);
|
|
530
785
|
} else {
|
|
531
786
|
lines.push(" - The proxy uses your paired credentials (~/.kojee/config.json + keypair.json).");
|
|
532
787
|
}
|
|
@@ -534,13 +789,18 @@ function whatHappensNext(runtime, opts) {
|
|
|
534
789
|
return lines.join("\n");
|
|
535
790
|
}
|
|
536
791
|
async function configureClaudeCode(opts) {
|
|
537
|
-
const { runInit } = await import("./install-
|
|
792
|
+
const { runInit } = await import("./install-V7LSQCYZ.js");
|
|
538
793
|
const report = runInit({
|
|
539
794
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
540
795
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
541
796
|
// Token mode threads --token/--url into the written args (per-token
|
|
542
797
|
// keystore). Paired mode leaves both unset ⇒ args stay `["kojee-mcp"]`.
|
|
543
|
-
...opts.token && opts.url ? { token: opts.token, url: opts.url } : {}
|
|
798
|
+
...opts.token && opts.url ? { token: opts.token, url: opts.url } : {},
|
|
799
|
+
// Per-runtime paired mode (connect): embed --paired-config, overwrite a
|
|
800
|
+
// stale entry so a re-connect can never leave a launcher reading old creds,
|
|
801
|
+
// and ensureCli so a fresh box with no ~/.claude.json still gets the
|
|
802
|
+
// launcher WRITTEN (never a silent "not-found" that drops the wrong slot).
|
|
803
|
+
...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath, overwrite: true, ensureCli: true } : {}
|
|
544
804
|
});
|
|
545
805
|
recordRuntime("claude-code");
|
|
546
806
|
return { runtime: "claude-code", output: formatClaudeInit(report), exitCode: 0 };
|
|
@@ -549,6 +809,7 @@ function formatClaudeInit(report) {
|
|
|
549
809
|
const tick = (s) => {
|
|
550
810
|
if (s === "added") return "\u2713 added";
|
|
551
811
|
if (s === "already-installed") return "\u21BB already installed";
|
|
812
|
+
if (s === "updated") return "\u21BB updated";
|
|
552
813
|
if (s === "preserved-different") return "\u26A0 preserved (existing entry differs \u2014 left untouched)";
|
|
553
814
|
if (s === "not-found") return "\u2014 not found";
|
|
554
815
|
return s ?? "\u2014";
|
|
@@ -586,44 +847,60 @@ function configureCodex(opts) {
|
|
|
586
847
|
if (wh.error) {
|
|
587
848
|
return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
588
849
|
}
|
|
589
|
-
const
|
|
590
|
-
const
|
|
850
|
+
const connectPairedMode = !!opts.pairedConfigPath && !wh.url && !wh.secret;
|
|
851
|
+
const hasRealUrl = !!wh.url && !isPlaceholderWebhookUrl(wh.url);
|
|
852
|
+
const url = connectPairedMode || !hasRealUrl ? void 0 : wh.url;
|
|
853
|
+
const secret = connectPairedMode || !hasRealUrl ? void 0 : wh.secret || generateWebhookSecret();
|
|
591
854
|
const tokenArgs = opts.token && opts.url ? { token: opts.token, url: opts.url } : {};
|
|
855
|
+
const pairedArgs = opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {};
|
|
592
856
|
writeCodexConfig({
|
|
593
857
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
594
858
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
595
|
-
webhookUrl: url,
|
|
596
|
-
webhookSecret: secret,
|
|
859
|
+
...url !== void 0 ? { webhookUrl: url } : {},
|
|
860
|
+
...secret !== void 0 ? { webhookSecret: secret } : {},
|
|
597
861
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
598
|
-
...tokenArgs
|
|
862
|
+
...tokenArgs,
|
|
863
|
+
...pairedArgs
|
|
599
864
|
});
|
|
600
865
|
recordRuntime("codex");
|
|
601
866
|
const lines = [];
|
|
602
867
|
lines.push("Configured runtime: codex");
|
|
603
|
-
lines.push(
|
|
868
|
+
lines.push(
|
|
869
|
+
"Wake mode: self-contained (proxy-written pending marker) + stop-hook peek. Webhook OPTIONAL \u2014 an extra low-latency push path, never required."
|
|
870
|
+
);
|
|
604
871
|
lines.push("");
|
|
605
872
|
lines.push("Wrote [mcp_servers.kojee] to ~/.codex/config.toml:");
|
|
606
873
|
lines.push(indent(buildCodexMcpServerTable({
|
|
607
|
-
|
|
608
|
-
|
|
874
|
+
// In connectPairedMode the webhook env is left untouched — print neither key
|
|
875
|
+
// (they are preserved from the existing config, not owned this run).
|
|
876
|
+
...url !== void 0 ? { webhookUrl: url } : {},
|
|
877
|
+
...secret !== void 0 ? { webhookSecret: "<redacted>" } : {},
|
|
609
878
|
...wh.signatureEnv.length > 0 ? { signatureEnv: wh.signatureEnv } : {},
|
|
610
879
|
// Redact the gateway token in the human-readable printed copy (it ends up
|
|
611
880
|
// in result.output → cli.ts console.error). The WRITTEN config above keeps
|
|
612
881
|
// the real token; only this printed report is redacted, like webhookSecret.
|
|
613
|
-
...tokenArgs.token ? { token: "<redacted>", url: tokenArgs.url } : {}
|
|
882
|
+
...tokenArgs.token ? { token: "<redacted>", url: tokenArgs.url } : {},
|
|
883
|
+
// A paired-config path is not a secret — print it verbatim.
|
|
884
|
+
...pairedArgs
|
|
614
885
|
})));
|
|
615
886
|
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
616
887
|
lines.push("");
|
|
617
888
|
lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
|
|
618
889
|
lines.push(indent(buildCodexStopHookBlock()));
|
|
619
890
|
lines.push("");
|
|
620
|
-
lines.push(
|
|
891
|
+
lines.push(
|
|
892
|
+
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)"}`
|
|
893
|
+
);
|
|
621
894
|
lines.push(`bounded-listen cap: ${CODEX_LISTEN_CAP_MS}ms (the model picks listen vs drain vs ignore).`);
|
|
622
895
|
lines.push("");
|
|
623
896
|
lines.push("Next steps (codex):");
|
|
624
897
|
lines.push(" Restart Codex (or start a new `codex` / `codex exec` session) to load the MCP server and hook.");
|
|
625
|
-
|
|
626
|
-
|
|
898
|
+
if (wh.url) {
|
|
899
|
+
lines.push(" Stand up your webhook receiver at KOJEE_WEBHOOK_URL \u2014 contract:");
|
|
900
|
+
lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
|
|
901
|
+
} else {
|
|
902
|
+
lines.push(" (Optional) For an extra low-latency push path, re-run with --webhook-url <receiver>; wake works without it.");
|
|
903
|
+
}
|
|
627
904
|
lines.push(" Verify: kojee-mcp doctor");
|
|
628
905
|
lines.push("");
|
|
629
906
|
lines.push(CODEX_UNVERIFIED_NOTE);
|
|
@@ -652,7 +929,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
|
|
|
652
929
|
return lines;
|
|
653
930
|
}
|
|
654
931
|
function distDir() {
|
|
655
|
-
return
|
|
932
|
+
return path7.dirname(fileURLToPath2(import.meta.url));
|
|
656
933
|
}
|
|
657
934
|
function resolveBinPath() {
|
|
658
935
|
const entry = process.argv[1];
|
|
@@ -660,14 +937,30 @@ function resolveBinPath() {
|
|
|
660
937
|
}
|
|
661
938
|
function configureHermes(opts) {
|
|
662
939
|
const runtime = "hermes";
|
|
663
|
-
const
|
|
940
|
+
const env0 = opts.env ?? process.env;
|
|
941
|
+
const suppliedUrl = (opts.webhookUrl ?? env0["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
942
|
+
const effectiveOpts = suppliedUrl ? opts : { ...opts, webhookUrl: HERMES_DEFAULT_WEBHOOK_URL };
|
|
943
|
+
const wh = resolveWizardWebhook(effectiveOpts);
|
|
664
944
|
if (wh.error) {
|
|
665
945
|
return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
666
946
|
}
|
|
947
|
+
opts = effectiveOpts;
|
|
667
948
|
const lines = [];
|
|
668
949
|
lines.push(`Configured runtime: ${runtime}`);
|
|
669
950
|
lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
|
|
670
951
|
lines.push("");
|
|
952
|
+
if (!suppliedUrl) {
|
|
953
|
+
lines.push(
|
|
954
|
+
"WARNING: no --webhook-url was supplied, so the webhook sink DEFAULTS to the fixed",
|
|
955
|
+
`loopback port ${HERMES_DEFAULT_WEBHOOK_URL} (the hermes-plugin listener). Every Tandem`,
|
|
956
|
+
"event body is POSTed there. On a multi-user host, another local user or process that",
|
|
957
|
+
"binds that port while the hermes plugin is down (before first start, or after a crash)",
|
|
958
|
+
"receives those event bodies plus valid signature/body pairs replayable against the real",
|
|
959
|
+
"receiver. If that risk applies to this host, re-run with an explicit --webhook-url",
|
|
960
|
+
"pointing at a receiver you control, and keep the hermes plugin running.",
|
|
961
|
+
""
|
|
962
|
+
);
|
|
963
|
+
}
|
|
671
964
|
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
672
965
|
if (wh.url) {
|
|
673
966
|
const home = kojeeHomeDir();
|
|
@@ -697,10 +990,13 @@ function configureHermes(opts) {
|
|
|
697
990
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
698
991
|
}
|
|
699
992
|
recordRuntime(runtime);
|
|
700
|
-
const envFile =
|
|
993
|
+
const envFile = path7.join(home, ".kojee", "hermes.env");
|
|
701
994
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
702
995
|
lines.push("");
|
|
703
996
|
lines.push(install.output);
|
|
997
|
+
lines.push("");
|
|
998
|
+
lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
|
|
999
|
+
lines.push(indent(buildCondensedTandemRules()));
|
|
704
1000
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
705
1001
|
}
|
|
706
1002
|
recordRuntime(runtime);
|
|
@@ -710,33 +1006,19 @@ function configureHermes(opts) {
|
|
|
710
1006
|
lines.push(" Re-run with --webhook-url once your receiver is up to complete the install");
|
|
711
1007
|
lines.push(" (writes both env files, copies the plugin, installs the daemon service).");
|
|
712
1008
|
lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
|
|
1009
|
+
lines.push("");
|
|
1010
|
+
lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
|
|
1011
|
+
lines.push(indent(buildCondensedTandemRules()));
|
|
713
1012
|
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
714
1013
|
}
|
|
715
1014
|
function openclawConfigPath(opts) {
|
|
716
|
-
return opts.openclawConfigPath ??
|
|
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;
|
|
1015
|
+
return opts.openclawConfigPath ?? path7.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
|
|
727
1016
|
}
|
|
728
1017
|
function configureOpenclaw(opts) {
|
|
729
1018
|
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
1019
|
const install = installOpenclaw({
|
|
735
|
-
homeDir: kojeeHomeDir(),
|
|
736
1020
|
openclawConfigPath: openclawConfigPath(opts),
|
|
737
|
-
...
|
|
738
|
-
...token ? { token } : {},
|
|
739
|
-
...pluginSourceDir ? { pluginSourceDir } : {}
|
|
1021
|
+
...opts.pairedConfigPath ? { pairedConfigPath: opts.pairedConfigPath } : {}
|
|
740
1022
|
});
|
|
741
1023
|
recordRuntime(runtime);
|
|
742
1024
|
return { runtime, output: install.output, exitCode: install.exitCode };
|
|
@@ -745,7 +1027,7 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
745
1027
|
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
746
1028
|
const lines = [`Uninstalling runtime: ${effective}`];
|
|
747
1029
|
if (effective === "claude-code") {
|
|
748
|
-
const { runUninstall } = await import("./install-
|
|
1030
|
+
const { runUninstall } = await import("./install-V7LSQCYZ.js");
|
|
749
1031
|
const report = runUninstall({
|
|
750
1032
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
751
1033
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
@@ -774,13 +1056,15 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
774
1056
|
} else {
|
|
775
1057
|
lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
|
|
776
1058
|
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
777
|
-
const envPath =
|
|
1059
|
+
const envPath = path7.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
778
1060
|
try {
|
|
779
|
-
|
|
1061
|
+
fs6.unlinkSync(envPath);
|
|
780
1062
|
lines.push(` removed ${envPath}`);
|
|
781
1063
|
} catch {
|
|
782
1064
|
}
|
|
783
1065
|
}
|
|
1066
|
+
const skillLine = removeSkillForRuntime(effective, opts.skillsRoot);
|
|
1067
|
+
if (skillLine) lines.push(skillLine);
|
|
784
1068
|
clearRuntimeRecord();
|
|
785
1069
|
return { runtime: effective, output: lines.join("\n"), exitCode: 0 };
|
|
786
1070
|
}
|
|
@@ -824,119 +1108,13 @@ registerBuiltinInstaller(
|
|
|
824
1108
|
registerBuiltinInstaller(
|
|
825
1109
|
"openclaw",
|
|
826
1110
|
"OpenClaw",
|
|
827
|
-
["pair-credential", "
|
|
1111
|
+
["pair-credential", "mcp-config-write"],
|
|
828
1112
|
(o) => configureOpenclaw(o)
|
|
829
1113
|
);
|
|
830
1114
|
|
|
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
1115
|
export {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1116
|
+
reconcileConnectPairSlot,
|
|
1117
|
+
pairSlotFor,
|
|
1118
|
+
DEFAULT_BROKER_URL,
|
|
1119
|
+
runWizard
|
|
942
1120
|
};
|