kojee-mcp 0.7.0 → 0.7.2
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/chunk-5DHIUN73.js +25 -0
- package/dist/{chunk-OB5T6P24.js → chunk-E35VWFZV.js} +1 -1
- package/dist/{chunk-GVWYW7MY.js → chunk-EDHG4375.js} +241 -58
- package/dist/{chunk-FSKGQ6GT.js → chunk-HI42GBQ3.js} +5 -0
- package/dist/chunk-IZN7IZPW.js +132 -0
- package/dist/{chunk-5H75AEZ2.js → chunk-R5GC2GRD.js} +62 -11
- package/dist/{chunk-OBIJ6WQP.js → chunk-RVLUZLXD.js} +63 -22
- package/dist/{chunk-QJFMU4QC.js → chunk-TMCNB4JH.js} +1 -1
- package/dist/{chunk-QEJUNP3X.js → chunk-XFGGMDZ4.js} +6 -5
- package/dist/chunk-XLRF5ATG.js +103 -0
- package/dist/{chunk-SGRVG4HW.js → chunk-ZUIYFRO5.js} +62 -30
- package/dist/cli.js +24 -14
- package/dist/codex-prompt-submit-hook-FOBPGZHJ.js +39 -0
- package/dist/codex-stop-hook-PNWYNQKM.js +136 -0
- package/dist/{connect-handler-HIRM624N.js → connect-handler-2JFIMQY6.js} +15 -7
- package/dist/{doctor-PLKLHTJM.js → doctor-HXMCO5PR.js} +2 -2
- package/dist/doctor-codex-ZSALZPH3.js +370 -0
- package/dist/{event-stream-KRYWEYWO.js → event-stream-WPN3EN7C.js} +5 -1
- package/dist/index.js +6 -5
- package/dist/{install-V7LSQCYZ.js → install-GGI6GU6C.js} +1 -1
- package/dist/lib.d.ts +40 -15
- package/dist/lib.js +7 -7
- package/dist/pending-state-6TVRR63P.js +134 -0
- package/dist/{registry-VRFHKOPP.js → registry-42Y45L6I.js} +114 -47
- package/dist/{server-YTHOMA4H.js → server-JWIH7OFA.js} +5 -2
- package/dist/{setup-handler-UHVKSX7E.js → setup-handler-GBPXDETR.js} +8 -7
- package/dist/{stop-hook-YI4KRKF2.js → stop-hook-PWTAP227.js} +2 -2
- package/dist/{tail-stream-NBGHHBS4.js → tail-stream-N43D53RC.js} +99 -19
- package/package.json +3 -2
- package/skills/using-tandems/SKILL.md +52 -0
- package/dist/chunk-WIU5WGDF.js +0 -10
- package/dist/codex-stop-hook-EOMQD3J4.js +0 -69
- package/dist/doctor-codex-HARAAH4Y.js +0 -154
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var FALLBACK_VERSION = "0.0.0-unknown";
|
|
6
|
+
function resolveVersion() {
|
|
7
|
+
try {
|
|
8
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const parsed = JSON.parse(
|
|
10
|
+
fs.readFileSync(path.join(here, "..", "package.json"), "utf8")
|
|
11
|
+
);
|
|
12
|
+
return typeof parsed?.version === "string" && parsed.version ? parsed.version : FALLBACK_VERSION;
|
|
13
|
+
} catch (err) {
|
|
14
|
+
process.stderr.write(
|
|
15
|
+
`kojee-mcp: could not resolve version from package.json, falling back to ${FALLBACK_VERSION}: ${String(err)}
|
|
16
|
+
`
|
|
17
|
+
);
|
|
18
|
+
return FALLBACK_VERSION;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var VERSION = resolveVersion();
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
VERSION
|
|
25
|
+
};
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
removeOpenclawMcpServer,
|
|
3
|
-
writeOpenclawMcpConfig
|
|
4
|
-
} from "./chunk-E6WMFMM2.js";
|
|
5
1
|
import {
|
|
6
2
|
buildCodexMcpServerTable,
|
|
3
|
+
buildCodexPromptSubmitHookBlock,
|
|
7
4
|
buildCodexStopHookBlock,
|
|
5
|
+
isPlaceholderWebhookUrl,
|
|
8
6
|
removeCodexConfig,
|
|
9
7
|
writeCodexConfig
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RVLUZLXD.js";
|
|
9
|
+
import {
|
|
10
|
+
removeOpenclawMcpServer,
|
|
11
|
+
writeOpenclawMcpConfig
|
|
12
|
+
} from "./chunk-E6WMFMM2.js";
|
|
13
|
+
import {
|
|
14
|
+
WIZARD_RUNTIMES,
|
|
15
|
+
isWizardRuntime
|
|
16
|
+
} from "./chunk-77HWBSRH.js";
|
|
11
17
|
import {
|
|
12
18
|
CONNECT_RUNTIMES
|
|
13
19
|
} from "./chunk-EIH2LNF4.js";
|
|
@@ -19,10 +25,6 @@ import {
|
|
|
19
25
|
import {
|
|
20
26
|
kojeeHomeDir
|
|
21
27
|
} from "./chunk-SQL56SEB.js";
|
|
22
|
-
import {
|
|
23
|
-
WIZARD_RUNTIMES,
|
|
24
|
-
isWizardRuntime
|
|
25
|
-
} from "./chunk-77HWBSRH.js";
|
|
26
28
|
import {
|
|
27
29
|
resolveSignatureEmission,
|
|
28
30
|
resolveWebhookConfig
|
|
@@ -40,10 +42,14 @@ import {
|
|
|
40
42
|
secureFile,
|
|
41
43
|
unlinkSyncRetry
|
|
42
44
|
} from "./chunk-U5HHHRXA.js";
|
|
45
|
+
import {
|
|
46
|
+
VERSION
|
|
47
|
+
} from "./chunk-5DHIUN73.js";
|
|
43
48
|
import {
|
|
44
49
|
CODEX_LISTEN_CAP_MS,
|
|
50
|
+
buildCondensedTandemRules,
|
|
45
51
|
buildWebhookReceiverNote
|
|
46
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-XFGGMDZ4.js";
|
|
47
53
|
|
|
48
54
|
// src/wizard/pair-slot.ts
|
|
49
55
|
import fs from "fs";
|
|
@@ -128,10 +134,161 @@ function pairSlotFor(runtime, code) {
|
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
// src/wizard/wizard.ts
|
|
131
|
-
import
|
|
132
|
-
import
|
|
133
|
-
import
|
|
137
|
+
import crypto3 from "crypto";
|
|
138
|
+
import fs6 from "fs";
|
|
139
|
+
import path7 from "path";
|
|
140
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
141
|
+
|
|
142
|
+
// src/wizard/skill-install.ts
|
|
143
|
+
import crypto from "crypto";
|
|
144
|
+
import fs2 from "fs";
|
|
145
|
+
import path2 from "path";
|
|
134
146
|
import { fileURLToPath } from "url";
|
|
147
|
+
var SKILL_NAME = "using-tandems";
|
|
148
|
+
var SKILL_MARKER_PREFIX = "kojee-mcp:skill using-tandems";
|
|
149
|
+
function hashSkillBody(body) {
|
|
150
|
+
return crypto.createHash("sha256").update(body, "utf8").digest("hex").slice(0, 12);
|
|
151
|
+
}
|
|
152
|
+
function bundledSkillSourcePath() {
|
|
153
|
+
const here = path2.dirname(fileURLToPath(import.meta.url));
|
|
154
|
+
const candidates = [
|
|
155
|
+
path2.join(here, "..", "skills", SKILL_NAME, "SKILL.md"),
|
|
156
|
+
path2.join(here, "..", "..", "skills", SKILL_NAME, "SKILL.md")
|
|
157
|
+
];
|
|
158
|
+
for (const c of candidates) {
|
|
159
|
+
if (fs2.existsSync(c)) return c;
|
|
160
|
+
}
|
|
161
|
+
throw new Error(
|
|
162
|
+
`bundled skill '${SKILL_NAME}' not found (looked at: ${candidates.join(", ")}) \u2014 the package is missing its skills/ payload; reinstall kojee-mcp.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function readBundledSkill() {
|
|
166
|
+
const body = fs2.readFileSync(bundledSkillSourcePath(), "utf8").replace(/\s+$/, "") + "\n";
|
|
167
|
+
return { body, version: VERSION, hash: hashSkillBody(body) };
|
|
168
|
+
}
|
|
169
|
+
function markerLine(version, hash) {
|
|
170
|
+
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) -->`;
|
|
171
|
+
}
|
|
172
|
+
function renderInstalledSkill(b) {
|
|
173
|
+
return `${b.body}
|
|
174
|
+
${markerLine(b.version, hashSkillBody(b.body))}
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
var MARKER_RE = new RegExp(`<!-- ${SKILL_MARKER_PREFIX} v(\\S+) sha256:([0-9a-f]{12})[^>]*-->`);
|
|
178
|
+
function parseInstalledSkill(content) {
|
|
179
|
+
const m = MARKER_RE.exec(content);
|
|
180
|
+
if (!m) return null;
|
|
181
|
+
const before = content.slice(0, m.index);
|
|
182
|
+
const after = content.slice(m.index + m[0].length);
|
|
183
|
+
const body = (before + after).replace(/\s+$/, "") + "\n";
|
|
184
|
+
return { version: m[1], hash: m[2], body };
|
|
185
|
+
}
|
|
186
|
+
function compareVersions(a, b) {
|
|
187
|
+
const parse = (v) => (v.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
188
|
+
const x = parse(a);
|
|
189
|
+
const y = parse(b);
|
|
190
|
+
for (let i = 0; i < 3; i++) {
|
|
191
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
192
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
function readDest(dest) {
|
|
197
|
+
try {
|
|
198
|
+
return { existing: fs2.readFileSync(dest, "utf8"), unreadable: false };
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (err.code === "ENOENT") return { existing: null, unreadable: false };
|
|
201
|
+
return { existing: null, unreadable: true };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function installSkillFile(skillsRoot, bundled = readBundledSkill()) {
|
|
205
|
+
const dir = path2.join(skillsRoot, SKILL_NAME);
|
|
206
|
+
const dest = path2.join(dir, "SKILL.md");
|
|
207
|
+
const { existing, unreadable } = readDest(dest);
|
|
208
|
+
if (unreadable) return { status: "skipped-unreadable", path: dest };
|
|
209
|
+
if (existing === null) {
|
|
210
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
211
|
+
fs2.writeFileSync(dest, renderInstalledSkill(bundled));
|
|
212
|
+
return { status: "installed", path: dest };
|
|
213
|
+
}
|
|
214
|
+
const parsed = parseInstalledSkill(existing);
|
|
215
|
+
if (!parsed) return { status: "skipped-unmanaged", path: dest };
|
|
216
|
+
if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
|
|
217
|
+
const cmp = compareVersions(bundled.version, parsed.version);
|
|
218
|
+
if (cmp < 0) return { status: "kept-newer", path: dest };
|
|
219
|
+
if (cmp === 0 && hashSkillBody(bundled.body) === parsed.hash)
|
|
220
|
+
return { status: "up-to-date", path: dest };
|
|
221
|
+
fs2.writeFileSync(dest, renderInstalledSkill(bundled));
|
|
222
|
+
return { status: "updated", path: dest };
|
|
223
|
+
}
|
|
224
|
+
function removeSkillFile(skillsRoot) {
|
|
225
|
+
const dir = path2.join(skillsRoot, SKILL_NAME);
|
|
226
|
+
const dest = path2.join(dir, "SKILL.md");
|
|
227
|
+
const { existing, unreadable } = readDest(dest);
|
|
228
|
+
if (unreadable) return { status: "skipped-unreadable", path: dest };
|
|
229
|
+
if (existing === null) return { status: "not-found", path: dest };
|
|
230
|
+
const parsed = parseInstalledSkill(existing);
|
|
231
|
+
if (!parsed) return { status: "skipped-unmanaged", path: dest };
|
|
232
|
+
if (hashSkillBody(parsed.body) !== parsed.hash) return { status: "skipped-modified", path: dest };
|
|
233
|
+
fs2.unlinkSync(dest);
|
|
234
|
+
try {
|
|
235
|
+
fs2.rmdirSync(dir);
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
return { status: "removed", path: dest };
|
|
239
|
+
}
|
|
240
|
+
function skillsRootFor(runtime, homeDir = kojeeHomeDir()) {
|
|
241
|
+
if (runtime === "claude-code") return path2.join(homeDir, ".claude", "skills");
|
|
242
|
+
if (runtime === "codex") return path2.join(homeDir, ".agents", "skills");
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
var LABEL = `skill (${SKILL_NAME})`;
|
|
246
|
+
function installSkillForRuntime(runtime, skillsRootOverride) {
|
|
247
|
+
const root = skillsRootOverride ?? skillsRootFor(runtime);
|
|
248
|
+
if (!root) return null;
|
|
249
|
+
try {
|
|
250
|
+
const r = installSkillFile(root);
|
|
251
|
+
switch (r.status) {
|
|
252
|
+
case "installed":
|
|
253
|
+
return `${LABEL}: \u2713 installed \u2192 ${r.path}`;
|
|
254
|
+
case "updated":
|
|
255
|
+
return `${LABEL}: \u21BB updated to v${VERSION} \u2192 ${r.path}`;
|
|
256
|
+
case "up-to-date":
|
|
257
|
+
return `${LABEL}: \u21BB up to date (v${VERSION})`;
|
|
258
|
+
case "kept-newer":
|
|
259
|
+
return `${LABEL}: \u2014 kept (installed copy is newer than this proxy's v${VERSION})`;
|
|
260
|
+
case "skipped-unmanaged":
|
|
261
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists without a kojee-mcp marker (not ours; left untouched)`;
|
|
262
|
+
case "skipped-modified":
|
|
263
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} was modified locally (edits preserved; delete the file to receive updates)`;
|
|
264
|
+
case "skipped-unreadable":
|
|
265
|
+
return `${LABEL}: \u26A0 skipped \u2014 ${r.path} exists but could not be read (not verifying ownership \u2192 not touching it)`;
|
|
266
|
+
}
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return `${LABEL}: WARNING \u2014 could not install (${err.message}); the runtime still works without it.`;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function removeSkillForRuntime(runtime, skillsRootOverride) {
|
|
272
|
+
const root = skillsRootOverride ?? skillsRootFor(runtime);
|
|
273
|
+
if (!root) return null;
|
|
274
|
+
try {
|
|
275
|
+
const r = removeSkillFile(root);
|
|
276
|
+
switch (r.status) {
|
|
277
|
+
case "removed":
|
|
278
|
+
return `${LABEL}: \u2713 removed ${r.path}`;
|
|
279
|
+
case "not-found":
|
|
280
|
+
return `${LABEL}: \u2014 not found`;
|
|
281
|
+
case "skipped-unmanaged":
|
|
282
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} has no kojee-mcp marker (not ours)`;
|
|
283
|
+
case "skipped-modified":
|
|
284
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} was modified locally (never deleting your edits; remove it manually)`;
|
|
285
|
+
case "skipped-unreadable":
|
|
286
|
+
return `${LABEL}: \u26A0 left in place \u2014 ${r.path} exists but could not be read (cannot verify it's ours)`;
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
return `${LABEL}: WARNING \u2014 could not remove (${err.message}).`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
135
292
|
|
|
136
293
|
// src/wizard/registry.ts
|
|
137
294
|
var installers = /* @__PURE__ */ new Map();
|
|
@@ -146,16 +303,16 @@ function runtimeUsesWebhook(id) {
|
|
|
146
303
|
}
|
|
147
304
|
|
|
148
305
|
// src/wizard/capabilities/webhook-secret.ts
|
|
149
|
-
import
|
|
150
|
-
import
|
|
151
|
-
import
|
|
306
|
+
import crypto2 from "crypto";
|
|
307
|
+
import fs3 from "fs";
|
|
308
|
+
import path3 from "path";
|
|
152
309
|
function generateSecret() {
|
|
153
|
-
return
|
|
310
|
+
return crypto2.randomBytes(32).toString("hex");
|
|
154
311
|
}
|
|
155
312
|
function readSecretFromEnv(envPath) {
|
|
156
313
|
let body;
|
|
157
314
|
try {
|
|
158
|
-
body =
|
|
315
|
+
body = fs3.readFileSync(envPath, "utf8");
|
|
159
316
|
} catch {
|
|
160
317
|
return void 0;
|
|
161
318
|
}
|
|
@@ -168,10 +325,10 @@ function shellSingleQuote(value) {
|
|
|
168
325
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
169
326
|
}
|
|
170
327
|
function upsertEnvFile(filePath, vars, opts = {}) {
|
|
171
|
-
|
|
328
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
172
329
|
let lines = [];
|
|
173
330
|
try {
|
|
174
|
-
lines =
|
|
331
|
+
lines = fs3.readFileSync(filePath, "utf8").split("\n");
|
|
175
332
|
} catch {
|
|
176
333
|
}
|
|
177
334
|
if (opts.header && !lines.some((l) => l === opts.header)) lines.unshift(opts.header);
|
|
@@ -183,7 +340,7 @@ function upsertEnvFile(filePath, vars, opts = {}) {
|
|
|
183
340
|
if (idx >= 0) lines[idx] = line;
|
|
184
341
|
else lines.push(line);
|
|
185
342
|
}
|
|
186
|
-
|
|
343
|
+
fs3.writeFileSync(filePath, lines.join("\n") + "\n", { mode: 384 });
|
|
187
344
|
}
|
|
188
345
|
function writeWebhookSecretBothSides(opts) {
|
|
189
346
|
const existing = opts.existingSecret ?? readSecretFromEnv(opts.daemonEnvPath) ?? readSecretFromEnv(opts.adapterEnvPath);
|
|
@@ -211,11 +368,11 @@ function writeWebhookSecretBothSides(opts) {
|
|
|
211
368
|
}
|
|
212
369
|
|
|
213
370
|
// src/wizard/installers/hermes.ts
|
|
214
|
-
import
|
|
371
|
+
import path6 from "path";
|
|
215
372
|
|
|
216
373
|
// src/wizard/plugin-payload.ts
|
|
217
|
-
import
|
|
218
|
-
import
|
|
374
|
+
import fs4 from "fs";
|
|
375
|
+
import path4 from "path";
|
|
219
376
|
var HERMES_PAYLOAD_FILES = [
|
|
220
377
|
"__init__.py",
|
|
221
378
|
"adapter.py",
|
|
@@ -223,24 +380,24 @@ var HERMES_PAYLOAD_FILES = [
|
|
|
223
380
|
"plugin.yaml"
|
|
224
381
|
];
|
|
225
382
|
function stagePluginPayload(opts) {
|
|
226
|
-
|
|
383
|
+
fs4.mkdirSync(opts.destDir, { recursive: true });
|
|
227
384
|
const written = [];
|
|
228
385
|
for (const file of opts.files) {
|
|
229
|
-
const src =
|
|
230
|
-
if (!
|
|
386
|
+
const src = path4.join(opts.srcDir, file);
|
|
387
|
+
if (!fs4.existsSync(src)) {
|
|
231
388
|
throw new Error(
|
|
232
389
|
`stagePluginPayload: missing payload file '${file}' in ${opts.srcDir}`
|
|
233
390
|
);
|
|
234
391
|
}
|
|
235
|
-
const dest =
|
|
236
|
-
|
|
392
|
+
const dest = path4.join(opts.destDir, file);
|
|
393
|
+
fs4.copyFileSync(src, dest);
|
|
237
394
|
written.push(dest);
|
|
238
395
|
}
|
|
239
396
|
return written;
|
|
240
397
|
}
|
|
241
398
|
function resolveBundledPayloadDir(runtime, baseDir) {
|
|
242
|
-
const dir =
|
|
243
|
-
if (!
|
|
399
|
+
const dir = path4.join(baseDir, "plugins", runtime);
|
|
400
|
+
if (!fs4.existsSync(dir) || !fs4.statSync(dir).isDirectory()) {
|
|
244
401
|
throw new Error(
|
|
245
402
|
`resolveBundledPayloadDir: no bundled payload for '${runtime}' at ${dir} \u2014 run the build (npm run build) so dist/plugins is staged.`
|
|
246
403
|
);
|
|
@@ -248,10 +405,10 @@ function resolveBundledPayloadDir(runtime, baseDir) {
|
|
|
248
405
|
return dir;
|
|
249
406
|
}
|
|
250
407
|
function missingPayloadFiles(runtime, baseDir, files) {
|
|
251
|
-
const dir =
|
|
252
|
-
const dirOk =
|
|
408
|
+
const dir = path4.join(baseDir, "plugins", runtime);
|
|
409
|
+
const dirOk = fs4.existsSync(dir) && fs4.statSync(dir).isDirectory();
|
|
253
410
|
if (!dirOk) return [...files];
|
|
254
|
-
return files.filter((f) => !
|
|
411
|
+
return files.filter((f) => !fs4.existsSync(path4.join(dir, f)));
|
|
255
412
|
}
|
|
256
413
|
function installBundledPayload(opts) {
|
|
257
414
|
const srcDir = resolveBundledPayloadDir(opts.runtime, opts.baseDir);
|
|
@@ -259,8 +416,8 @@ function installBundledPayload(opts) {
|
|
|
259
416
|
}
|
|
260
417
|
|
|
261
418
|
// src/wizard/service.ts
|
|
262
|
-
import
|
|
263
|
-
import
|
|
419
|
+
import fs5 from "fs";
|
|
420
|
+
import path5 from "path";
|
|
264
421
|
function systemdUnit(spec) {
|
|
265
422
|
return [
|
|
266
423
|
"[Unit]",
|
|
@@ -307,7 +464,7 @@ function launchdPlist(spec, label) {
|
|
|
307
464
|
}
|
|
308
465
|
function planService(platform, spec) {
|
|
309
466
|
if (platform === "linux") {
|
|
310
|
-
const unitPath =
|
|
467
|
+
const unitPath = path5.join(
|
|
311
468
|
spec.homeDir,
|
|
312
469
|
".config",
|
|
313
470
|
"systemd",
|
|
@@ -324,7 +481,7 @@ function planService(platform, spec) {
|
|
|
324
481
|
}
|
|
325
482
|
if (platform === "darwin") {
|
|
326
483
|
const label = darwinLabel(spec.serviceName);
|
|
327
|
-
const unitPath =
|
|
484
|
+
const unitPath = path5.join(spec.homeDir, "Library", "LaunchAgents", `${label}.plist`);
|
|
328
485
|
return {
|
|
329
486
|
supported: true,
|
|
330
487
|
unitPath,
|
|
@@ -344,23 +501,23 @@ function planService(platform, spec) {
|
|
|
344
501
|
function writeService(platform, spec) {
|
|
345
502
|
const plan = planService(platform, spec);
|
|
346
503
|
if (plan.supported && plan.unitPath && plan.unitContent !== void 0) {
|
|
347
|
-
|
|
348
|
-
|
|
504
|
+
fs5.mkdirSync(path5.dirname(plan.unitPath), { recursive: true });
|
|
505
|
+
fs5.writeFileSync(plan.unitPath, plan.unitContent, { mode: 420 });
|
|
349
506
|
}
|
|
350
507
|
return plan;
|
|
351
508
|
}
|
|
352
509
|
|
|
353
510
|
// src/wizard/installers/hermes.ts
|
|
354
511
|
function installHermes(inp) {
|
|
355
|
-
const daemonEnv =
|
|
356
|
-
const adapterEnv =
|
|
357
|
-
const pluginDir =
|
|
512
|
+
const daemonEnv = path6.join(inp.homeDir, ".kojee", "hermes.env");
|
|
513
|
+
const adapterEnv = path6.join(inp.homeDir, ".hermes", ".env");
|
|
514
|
+
const pluginDir = path6.join(inp.homeDir, ".hermes", "plugins", "kojee-tandem");
|
|
358
515
|
if (!inp.skipPayload) {
|
|
359
516
|
const missing = missingPayloadFiles("hermes", inp.payloadBaseDir, HERMES_PAYLOAD_FILES);
|
|
360
517
|
if (missing.length > 0) {
|
|
361
518
|
return {
|
|
362
519
|
runtime: "hermes",
|
|
363
|
-
output: `hermes install ERROR: the bundled plugin payload is incomplete \u2014 missing ${missing.join(", ")} under ${
|
|
520
|
+
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.`,
|
|
364
521
|
exitCode: 2,
|
|
365
522
|
secret: "",
|
|
366
523
|
secretReused: false
|
|
@@ -442,7 +599,15 @@ function installOpenclaw(inp) {
|
|
|
442
599
|
" - Run OpenClaw as its GATEWAY DAEMON (its default mode) \u2014 the /wake injector only",
|
|
443
600
|
" wakes a running gateway. If the gateway is down, nudges are dropped until it restarts.",
|
|
444
601
|
" - 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"
|
|
602
|
+
" - Verify: openclaw mcp list (kojee should be listed) / kojee-mcp doctor",
|
|
603
|
+
"",
|
|
604
|
+
// Belt-and-suspenders: the same condensed rules ride the kojee MCP server's
|
|
605
|
+
// `instructions` field (buildNonChannelInstructions), but not every MCP host
|
|
606
|
+
// surfaces server instructions to the agent — print them here too so the
|
|
607
|
+
// operator can paste them into the agent's own instructions if needed.
|
|
608
|
+
"Agent ground rules (also delivered via the kojee MCP server's instructions;",
|
|
609
|
+
"add to your OpenClaw agent's instructions if it doesn't surface those):",
|
|
610
|
+
...buildCondensedTandemRules().split("\n").map((l) => ` ${l}`)
|
|
446
611
|
);
|
|
447
612
|
return { runtime: "openclaw", output: lines.join("\n"), exitCode: 0, configPath: inp.openclawConfigPath };
|
|
448
613
|
}
|
|
@@ -463,7 +628,7 @@ function uninstallOpenclaw(inp) {
|
|
|
463
628
|
var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
|
|
464
629
|
var HERMES_DEFAULT_WEBHOOK_URL = "http://127.0.0.1:8645/kojee-tandem";
|
|
465
630
|
function generateWebhookSecret() {
|
|
466
|
-
return
|
|
631
|
+
return crypto3.randomBytes(32).toString("hex");
|
|
467
632
|
}
|
|
468
633
|
async function resolveRuntime(opts) {
|
|
469
634
|
if (opts.runtime !== void 0) {
|
|
@@ -485,7 +650,8 @@ async function resolveRuntime(opts) {
|
|
|
485
650
|
}
|
|
486
651
|
function resolveWizardWebhook(opts) {
|
|
487
652
|
const env = opts.env ?? process.env;
|
|
488
|
-
const
|
|
653
|
+
const envUrl = (env["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
654
|
+
const url = (opts.webhookUrl ?? (isPlaceholderWebhookUrl(envUrl) ? "" : envUrl)).trim();
|
|
489
655
|
let secret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
490
656
|
if (url && !secret) secret = generateWebhookSecret();
|
|
491
657
|
const sigFormat = (opts.webhookSignatureFormat ?? env["KOJEE_WEBHOOK_SIGNATURE_FORMAT"] ?? "").trim();
|
|
@@ -597,14 +763,18 @@ async function runWizard(opts) {
|
|
|
597
763
|
}
|
|
598
764
|
const installer = getInstaller(runtime);
|
|
599
765
|
const result = installer ? await installer.install({ opts: effective }) : { runtime, output: `No installer registered for runtime '${runtime}'`, exitCode: 2 };
|
|
766
|
+
const skillLine = result.exitCode === 0 ? installSkillForRuntime(runtime, effective.skillsRoot) : null;
|
|
767
|
+
const body = skillLine ? `${result.output}
|
|
768
|
+
|
|
769
|
+
${skillLine}` : result.output;
|
|
600
770
|
if (preamble.length > 0 && result.exitCode === 0) {
|
|
601
771
|
return {
|
|
602
772
|
runtime,
|
|
603
|
-
output: [...preamble, "",
|
|
773
|
+
output: [...preamble, "", body, "", whatHappensNext(runtime, effective)].join("\n"),
|
|
604
774
|
exitCode: result.exitCode
|
|
605
775
|
};
|
|
606
776
|
}
|
|
607
|
-
return { runtime, output:
|
|
777
|
+
return { runtime, output: body, exitCode: result.exitCode };
|
|
608
778
|
}
|
|
609
779
|
function whatHappensNext(runtime, opts) {
|
|
610
780
|
const lines = ["What happens next:"];
|
|
@@ -620,7 +790,7 @@ function whatHappensNext(runtime, opts) {
|
|
|
620
790
|
return lines.join("\n");
|
|
621
791
|
}
|
|
622
792
|
async function configureClaudeCode(opts) {
|
|
623
|
-
const { runInit } = await import("./install-
|
|
793
|
+
const { runInit } = await import("./install-GGI6GU6C.js");
|
|
624
794
|
const report = runInit({
|
|
625
795
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
626
796
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
@@ -679,7 +849,7 @@ function configureCodex(opts) {
|
|
|
679
849
|
return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
680
850
|
}
|
|
681
851
|
const connectPairedMode = !!opts.pairedConfigPath && !wh.url && !wh.secret;
|
|
682
|
-
const hasRealUrl = !!wh.url;
|
|
852
|
+
const hasRealUrl = !!wh.url && !isPlaceholderWebhookUrl(wh.url);
|
|
683
853
|
const url = connectPairedMode || !hasRealUrl ? void 0 : wh.url;
|
|
684
854
|
const secret = connectPairedMode || !hasRealUrl ? void 0 : wh.secret || generateWebhookSecret();
|
|
685
855
|
const tokenArgs = opts.token && opts.url ? { token: opts.token, url: opts.url } : {};
|
|
@@ -716,8 +886,13 @@ function configureCodex(opts) {
|
|
|
716
886
|
})));
|
|
717
887
|
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
718
888
|
lines.push("");
|
|
719
|
-
lines.push("Wrote the Codex Stop
|
|
889
|
+
lines.push("Wrote the Codex Stop + UserPromptSubmit hooks (~/.codex/hooks.json; inline TOML form):");
|
|
720
890
|
lines.push(indent(buildCodexStopHookBlock()));
|
|
891
|
+
lines.push(indent(buildCodexPromptSubmitHookBlock()));
|
|
892
|
+
lines.push("");
|
|
893
|
+
lines.push(
|
|
894
|
+
"IMPORTANT: Codex will re-prompt to trust hooks on its next launch \u2014 approve it, or wakes silently stop."
|
|
895
|
+
);
|
|
721
896
|
lines.push("");
|
|
722
897
|
lines.push(
|
|
723
898
|
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)"}`
|
|
@@ -760,7 +935,7 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
|
|
|
760
935
|
return lines;
|
|
761
936
|
}
|
|
762
937
|
function distDir() {
|
|
763
|
-
return
|
|
938
|
+
return path7.dirname(fileURLToPath2(import.meta.url));
|
|
764
939
|
}
|
|
765
940
|
function resolveBinPath() {
|
|
766
941
|
const entry = process.argv[1];
|
|
@@ -821,10 +996,13 @@ function configureHermes(opts) {
|
|
|
821
996
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
822
997
|
}
|
|
823
998
|
recordRuntime(runtime);
|
|
824
|
-
const envFile =
|
|
999
|
+
const envFile = path7.join(home, ".kojee", "hermes.env");
|
|
825
1000
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
826
1001
|
lines.push("");
|
|
827
1002
|
lines.push(install.output);
|
|
1003
|
+
lines.push("");
|
|
1004
|
+
lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
|
|
1005
|
+
lines.push(indent(buildCondensedTandemRules()));
|
|
828
1006
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
829
1007
|
}
|
|
830
1008
|
recordRuntime(runtime);
|
|
@@ -834,10 +1012,13 @@ function configureHermes(opts) {
|
|
|
834
1012
|
lines.push(" Re-run with --webhook-url once your receiver is up to complete the install");
|
|
835
1013
|
lines.push(" (writes both env files, copies the plugin, installs the daemon service).");
|
|
836
1014
|
lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
|
|
1015
|
+
lines.push("");
|
|
1016
|
+
lines.push("Agent ground rules (add to your hermes agent's instructions \u2014 hermes has no skill loader):");
|
|
1017
|
+
lines.push(indent(buildCondensedTandemRules()));
|
|
837
1018
|
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
838
1019
|
}
|
|
839
1020
|
function openclawConfigPath(opts) {
|
|
840
|
-
return opts.openclawConfigPath ??
|
|
1021
|
+
return opts.openclawConfigPath ?? path7.join(kojeeHomeDir(), ".openclaw", "openclaw.json");
|
|
841
1022
|
}
|
|
842
1023
|
function configureOpenclaw(opts) {
|
|
843
1024
|
const runtime = "openclaw";
|
|
@@ -852,7 +1033,7 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
852
1033
|
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
853
1034
|
const lines = [`Uninstalling runtime: ${effective}`];
|
|
854
1035
|
if (effective === "claude-code") {
|
|
855
|
-
const { runUninstall } = await import("./install-
|
|
1036
|
+
const { runUninstall } = await import("./install-GGI6GU6C.js");
|
|
856
1037
|
const report = runUninstall({
|
|
857
1038
|
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
858
1039
|
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
@@ -881,13 +1062,15 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
881
1062
|
} else {
|
|
882
1063
|
lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
|
|
883
1064
|
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
884
|
-
const envPath =
|
|
1065
|
+
const envPath = path7.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
885
1066
|
try {
|
|
886
|
-
|
|
1067
|
+
fs6.unlinkSync(envPath);
|
|
887
1068
|
lines.push(` removed ${envPath}`);
|
|
888
1069
|
} catch {
|
|
889
1070
|
}
|
|
890
1071
|
}
|
|
1072
|
+
const skillLine = removeSkillForRuntime(effective, opts.skillsRoot);
|
|
1073
|
+
if (skillLine) lines.push(skillLine);
|
|
891
1074
|
clearRuntimeRecord();
|
|
892
1075
|
return { runtime: effective, output: lines.join("\n"), exitCode: 0 };
|
|
893
1076
|
}
|
|
@@ -41,6 +41,9 @@ function createAdaptiveWatchdog(options = {}) {
|
|
|
41
41
|
},
|
|
42
42
|
armedThresholdMs() {
|
|
43
43
|
return thresholdMs();
|
|
44
|
+
},
|
|
45
|
+
resetCadenceAnchor() {
|
|
46
|
+
prevHeartbeatAt = null;
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
49
|
}
|
|
@@ -443,9 +446,11 @@ function normalizeBackendEvent(raw, sseEventType) {
|
|
|
443
446
|
}
|
|
444
447
|
|
|
445
448
|
export {
|
|
449
|
+
UNARMED_FALLBACK_MS,
|
|
446
450
|
UNDICI_DEFAULT_BODY_TIMEOUT_MS,
|
|
447
451
|
createAdaptiveWatchdog,
|
|
448
452
|
createBackoffController,
|
|
453
|
+
LOG_HEARTBEAT_INTERVAL_MS,
|
|
449
454
|
startEventStream,
|
|
450
455
|
statusReason,
|
|
451
456
|
serializeCursorMap,
|