kojee-mcp 0.5.0 → 0.5.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/README.md +81 -4
- package/dist/{chunk-VLZADEFC.js → chunk-2TUAFAIW.js} +6 -9
- package/dist/chunk-BLEGIR35.js +43 -0
- package/dist/chunk-C6GZ2L2W.js +38 -0
- package/dist/{chunk-W6YRLSD4.js → chunk-DO42NPNR.js} +9 -16
- package/dist/chunk-EW72ZNQL.js +39 -0
- package/dist/chunk-F7L25L2J.js +60 -0
- package/dist/chunk-LVL25VLO.js +22 -0
- package/dist/chunk-SQL56SEB.js +14 -0
- package/dist/{chunk-QB22PD6T.js → chunk-WBMX4CHB.js} +29 -9
- package/dist/{chunk-LCFCCWMM.js → chunk-YEC7IHIG.js} +136 -78
- package/dist/{chunk-GBOTBYEP.js → chunk-YH27B6SW.js} +7 -8
- package/dist/chunk-ZW4SW7LJ.js +225 -0
- package/dist/cli.js +54 -80
- package/dist/codex-stop-hook-JOTBCS5K.js +72 -0
- package/dist/{doctor-GILTOH2R.js → doctor-TSHOMT5X.js} +29 -14
- package/dist/doctor-codex-BMI5JOO6.js +130 -0
- package/dist/{event-log-R6VW6GAF.js → event-log-RSTM4PLL.js} +3 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -3
- package/dist/{install-D2HIPOMT.js → install-WBIUVBZW.js} +5 -4
- package/dist/{paired-config-RB4SABOS.js → paired-config-JTFLHMZ2.js} +2 -1
- package/dist/runtime-record-WO4IECM6.js +14 -0
- package/dist/runtimes-CO43XUUK.js +12 -0
- package/dist/{session-discovery-QE5TTAPS.js → session-discovery-FNMJGFPM.js} +2 -1
- package/dist/{stop-hook-VLQS6QPR.js → stop-hook-SEPWWETV.js} +6 -5
- package/dist/{tail-stream-UZ42UIWO.js → tail-stream-BYKO4DW6.js} +4 -3
- package/dist/{user-prompt-submit-hook-C42DPDBO.js → user-prompt-submit-hook-ARPEO6FF.js} +2 -1
- package/dist/webhook-config-5TLLX7RA.js +10 -0
- package/dist/webhook-sink-7OYZBWXA.js +163 -0
- package/dist/wizard-7KHD5JT4.js +265 -0
- package/package.json +1 -1
- package/dist/chunk-E26AHU6J.js +0 -27
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/tandem/webhook-sink.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { ulid } from "ulidx";
|
|
4
|
+
var DEFAULT_MAX_QUEUE = 1e3;
|
|
5
|
+
var BACKOFF_BASE_MS = 500;
|
|
6
|
+
var BACKOFF_CEILING_MS = 3e4;
|
|
7
|
+
function computeWebhookSignature(secret, rawBody) {
|
|
8
|
+
return crypto.createHmac("sha256", secret).update(Buffer.from(rawBody, "utf8")).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
function defaultBackoffMs(attempt) {
|
|
11
|
+
const exp = Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CEILING_MS);
|
|
12
|
+
return Math.random() * exp;
|
|
13
|
+
}
|
|
14
|
+
function classifyStatus(status) {
|
|
15
|
+
if (status >= 200 && status < 300) return "success";
|
|
16
|
+
if (status === 408 || status === 429) return "retry";
|
|
17
|
+
if (status >= 500) return "retry";
|
|
18
|
+
return "permanent";
|
|
19
|
+
}
|
|
20
|
+
function sleep(ms) {
|
|
21
|
+
if (ms <= 0) return Promise.resolve();
|
|
22
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
23
|
+
}
|
|
24
|
+
function createWebhookSink(config, options = {}) {
|
|
25
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
26
|
+
const maxQueue = options.maxQueue ?? DEFAULT_MAX_QUEUE;
|
|
27
|
+
const backoffMs = options.backoffMs ?? defaultBackoffMs;
|
|
28
|
+
const log = options.log ?? ((line) => console.error(`[webhook] ${line}`));
|
|
29
|
+
const queue = [];
|
|
30
|
+
let inFlight = false;
|
|
31
|
+
let stopped = false;
|
|
32
|
+
let draining = false;
|
|
33
|
+
const stats = {
|
|
34
|
+
delivered: 0,
|
|
35
|
+
dropped: 0,
|
|
36
|
+
overflowDropped: 0,
|
|
37
|
+
queueDepth: 0,
|
|
38
|
+
lastOutcome: "none",
|
|
39
|
+
lastAttemptAt: null
|
|
40
|
+
};
|
|
41
|
+
let idleResolvers = [];
|
|
42
|
+
function maybeResolveIdle() {
|
|
43
|
+
if (queue.length === 0 && !inFlight) {
|
|
44
|
+
const r = idleResolvers;
|
|
45
|
+
idleResolvers = [];
|
|
46
|
+
for (const fn of r) fn();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function currentDepth() {
|
|
50
|
+
return queue.length + (inFlight ? 1 : 0);
|
|
51
|
+
}
|
|
52
|
+
function prepare(event) {
|
|
53
|
+
const body = JSON.stringify(event);
|
|
54
|
+
const signature = computeWebhookSignature(config.secret, body);
|
|
55
|
+
const deliveryId = event.id || ulid();
|
|
56
|
+
return { event, body, signature, deliveryId };
|
|
57
|
+
}
|
|
58
|
+
async function attempt(item) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timer = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetchImpl(config.url, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
// hex SHA-256 HMAC of the RAW body bytes — the receiver re-verifies.
|
|
67
|
+
"X-Kojee-Signature": item.signature,
|
|
68
|
+
"X-Kojee-Delivery": item.deliveryId
|
|
69
|
+
},
|
|
70
|
+
body: item.body,
|
|
71
|
+
signal: controller.signal
|
|
72
|
+
});
|
|
73
|
+
return classifyStatus(res.status);
|
|
74
|
+
} catch {
|
|
75
|
+
return "retry";
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function deliver(item) {
|
|
81
|
+
for (let n = 0; n <= config.maxRetries; n++) {
|
|
82
|
+
if (stopped) return;
|
|
83
|
+
const outcome = await attempt(item);
|
|
84
|
+
if (outcome === "success") {
|
|
85
|
+
stats.delivered += 1;
|
|
86
|
+
stats.lastOutcome = "delivered";
|
|
87
|
+
stats.lastAttemptAt = Date.now();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (outcome === "permanent") {
|
|
91
|
+
stats.dropped += 1;
|
|
92
|
+
stats.lastOutcome = "dropped";
|
|
93
|
+
stats.lastAttemptAt = Date.now();
|
|
94
|
+
log(`delivery=${item.deliveryId} dropped: permanent failure (4xx rejection), no retry`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (n < config.maxRetries) {
|
|
98
|
+
await sleep(backoffMs(n));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
stats.dropped += 1;
|
|
102
|
+
stats.lastOutcome = "dropped";
|
|
103
|
+
stats.lastAttemptAt = Date.now();
|
|
104
|
+
log(`delivery=${item.deliveryId} dropped: retries exhausted (${config.maxRetries})`);
|
|
105
|
+
}
|
|
106
|
+
async function drain() {
|
|
107
|
+
if (draining) return;
|
|
108
|
+
draining = true;
|
|
109
|
+
try {
|
|
110
|
+
while (queue.length > 0 && !stopped) {
|
|
111
|
+
const item = queue.shift();
|
|
112
|
+
inFlight = true;
|
|
113
|
+
stats.queueDepth = currentDepth();
|
|
114
|
+
try {
|
|
115
|
+
await deliver(item);
|
|
116
|
+
} catch {
|
|
117
|
+
stats.dropped += 1;
|
|
118
|
+
} finally {
|
|
119
|
+
inFlight = false;
|
|
120
|
+
stats.queueDepth = currentDepth();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
draining = false;
|
|
125
|
+
maybeResolveIdle();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
enqueue(event) {
|
|
130
|
+
try {
|
|
131
|
+
if (stopped) return;
|
|
132
|
+
if (queue.length >= maxQueue) {
|
|
133
|
+
stats.overflowDropped += 1;
|
|
134
|
+
log(`queue overflow (cap=${maxQueue}) \u2014 dropping newest event; receiver will redeliver on replay`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
queue.push(prepare(event));
|
|
138
|
+
stats.queueDepth = currentDepth();
|
|
139
|
+
void drain();
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
stats() {
|
|
144
|
+
return { ...stats, queueDepth: currentDepth() };
|
|
145
|
+
},
|
|
146
|
+
configSummary() {
|
|
147
|
+
return config.redactedSummary;
|
|
148
|
+
},
|
|
149
|
+
idle() {
|
|
150
|
+
if (queue.length === 0 && !inFlight) return Promise.resolve();
|
|
151
|
+
return new Promise((resolve) => idleResolvers.push(resolve));
|
|
152
|
+
},
|
|
153
|
+
async stop() {
|
|
154
|
+
stopped = true;
|
|
155
|
+
queue.length = 0;
|
|
156
|
+
maybeResolveIdle();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
computeWebhookSignature,
|
|
162
|
+
createWebhookSink
|
|
163
|
+
};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCodexMcpServerTable,
|
|
3
|
+
buildCodexStopHookBlock,
|
|
4
|
+
removeCodexConfig,
|
|
5
|
+
writeCodexConfig
|
|
6
|
+
} from "./chunk-ZW4SW7LJ.js";
|
|
7
|
+
import {
|
|
8
|
+
WIZARD_RUNTIMES,
|
|
9
|
+
isWizardRuntime
|
|
10
|
+
} from "./chunk-LVL25VLO.js";
|
|
11
|
+
import {
|
|
12
|
+
clearRuntimeRecord,
|
|
13
|
+
readRecordedRuntime,
|
|
14
|
+
recordRuntime
|
|
15
|
+
} from "./chunk-EW72ZNQL.js";
|
|
16
|
+
import {
|
|
17
|
+
kojeeHomeDir
|
|
18
|
+
} from "./chunk-SQL56SEB.js";
|
|
19
|
+
import {
|
|
20
|
+
CODEX_LISTEN_CAP_MS,
|
|
21
|
+
buildWebhookReceiverNote
|
|
22
|
+
} from "./chunk-C6GZ2L2W.js";
|
|
23
|
+
import {
|
|
24
|
+
secureFile
|
|
25
|
+
} from "./chunk-BLEGIR35.js";
|
|
26
|
+
import {
|
|
27
|
+
resolveWebhookConfig
|
|
28
|
+
} from "./chunk-F7L25L2J.js";
|
|
29
|
+
|
|
30
|
+
// src/wizard/wizard.ts
|
|
31
|
+
import crypto from "crypto";
|
|
32
|
+
import fs from "fs";
|
|
33
|
+
import path from "path";
|
|
34
|
+
function generateWebhookSecret() {
|
|
35
|
+
return crypto.randomBytes(32).toString("hex");
|
|
36
|
+
}
|
|
37
|
+
async function resolveRuntime(opts) {
|
|
38
|
+
if (opts.runtime !== void 0) {
|
|
39
|
+
if (!isWizardRuntime(opts.runtime)) {
|
|
40
|
+
return {
|
|
41
|
+
error: `Unknown --runtime "${opts.runtime}". Expected one of: ${WIZARD_RUNTIMES.join(", ")}.`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { runtime: opts.runtime };
|
|
45
|
+
}
|
|
46
|
+
if (opts.interactive && opts.promptRuntime) {
|
|
47
|
+
const picked = await opts.promptRuntime();
|
|
48
|
+
if (!isWizardRuntime(picked)) {
|
|
49
|
+
return { error: `Unknown runtime "${picked}". Expected one of: ${WIZARD_RUNTIMES.join(", ")}.` };
|
|
50
|
+
}
|
|
51
|
+
return { runtime: picked };
|
|
52
|
+
}
|
|
53
|
+
return { runtime: "claude-code" };
|
|
54
|
+
}
|
|
55
|
+
function resolveWizardWebhook(opts) {
|
|
56
|
+
const env = opts.env ?? process.env;
|
|
57
|
+
const url = (opts.webhookUrl ?? env["KOJEE_WEBHOOK_URL"] ?? "").trim();
|
|
58
|
+
let secret = (opts.webhookSecret ?? env["KOJEE_WEBHOOK_SECRET"] ?? "").trim();
|
|
59
|
+
if (url && !secret) secret = generateWebhookSecret();
|
|
60
|
+
const resolution = resolveWebhookConfig({
|
|
61
|
+
KOJEE_WEBHOOK_URL: url,
|
|
62
|
+
KOJEE_WEBHOOK_SECRET: secret,
|
|
63
|
+
...env["KOJEE_WEBHOOK_TIMEOUT_MS"] !== void 0 ? { KOJEE_WEBHOOK_TIMEOUT_MS: env["KOJEE_WEBHOOK_TIMEOUT_MS"] } : {},
|
|
64
|
+
...env["KOJEE_WEBHOOK_MAX_RETRIES"] !== void 0 ? { KOJEE_WEBHOOK_MAX_RETRIES: env["KOJEE_WEBHOOK_MAX_RETRIES"] } : {}
|
|
65
|
+
});
|
|
66
|
+
if (resolution.error) {
|
|
67
|
+
return { url, secret, redactedSummary: "", error: resolution.error };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
url,
|
|
71
|
+
secret,
|
|
72
|
+
redactedSummary: resolution.config?.redactedSummary ?? `url=${url} secret=<redacted>`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function writeRuntimeEnvFile(runtime, url, secret) {
|
|
76
|
+
const envPath = path.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
|
|
77
|
+
const body = [
|
|
78
|
+
`# kojee daemon env for runtime=${runtime} (source this before starting the daemon)`,
|
|
79
|
+
`export KOJEE_RUNTIME="${runtime}"`,
|
|
80
|
+
`export KOJEE_WEBHOOK_URL="${url}"`,
|
|
81
|
+
`export KOJEE_WEBHOOK_SECRET="${secret}"`,
|
|
82
|
+
""
|
|
83
|
+
].join("\n");
|
|
84
|
+
fs.mkdirSync(path.dirname(envPath), { recursive: true, mode: 448 });
|
|
85
|
+
fs.writeFileSync(envPath, body, { mode: 384 });
|
|
86
|
+
secureFile(envPath);
|
|
87
|
+
return envPath;
|
|
88
|
+
}
|
|
89
|
+
var CODEX_UNVERIFIED_NOTE = "NOTE: live Codex verification (hook fires, MCP server connects, bounded listen works) has not been run on this build \u2014 confirm in a real Codex session. This is the owner morning step.";
|
|
90
|
+
async function runWizard(opts) {
|
|
91
|
+
const resolved = await resolveRuntime(opts);
|
|
92
|
+
if ("error" in resolved) {
|
|
93
|
+
return { runtime: "claude-code", output: resolved.error, exitCode: 2 };
|
|
94
|
+
}
|
|
95
|
+
const runtime = resolved.runtime;
|
|
96
|
+
if (opts.uninstall) {
|
|
97
|
+
return runWizardUninstall(runtime, opts);
|
|
98
|
+
}
|
|
99
|
+
if (runtime === "claude-code") return configureClaudeCode(opts);
|
|
100
|
+
if (runtime === "codex") return configureCodex(opts);
|
|
101
|
+
return configureWebhookDaemon(runtime, opts);
|
|
102
|
+
}
|
|
103
|
+
async function configureClaudeCode(opts) {
|
|
104
|
+
const { runInit } = await import("./install-WBIUVBZW.js");
|
|
105
|
+
const report = runInit({
|
|
106
|
+
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
107
|
+
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
108
|
+
});
|
|
109
|
+
recordRuntime("claude-code");
|
|
110
|
+
return { runtime: "claude-code", output: formatClaudeInit(report), exitCode: 0 };
|
|
111
|
+
}
|
|
112
|
+
function formatClaudeInit(report) {
|
|
113
|
+
const tick = (s) => {
|
|
114
|
+
if (s === "added") return "\u2713 added";
|
|
115
|
+
if (s === "already-installed") return "\u21BB already installed";
|
|
116
|
+
if (s === "preserved-different") return "\u26A0 preserved (existing entry differs \u2014 left untouched)";
|
|
117
|
+
if (s === "not-found") return "\u2014 not found";
|
|
118
|
+
return s ?? "\u2014";
|
|
119
|
+
};
|
|
120
|
+
const lines = ["Configured runtime: claude-code", "", "Installing kojee for Claude:"];
|
|
121
|
+
for (const t of report.targets) {
|
|
122
|
+
const label = t.kind === "cli" ? "CLI" : "Claude.app";
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(` ${t.path} (${label})`);
|
|
125
|
+
lines.push(` mcpServers.kojee ${tick(t.mcpServer)}`);
|
|
126
|
+
if (t.kind === "cli") {
|
|
127
|
+
if (t.hooksPath) lines.push(` ${t.hooksPath} (hooks)`);
|
|
128
|
+
lines.push(` hooks.Stop ${tick(t.stopHook)}`);
|
|
129
|
+
lines.push(` hooks.UserPromptSubmit ${tick(t.userPromptSubmitHook)}`);
|
|
130
|
+
} else {
|
|
131
|
+
lines.push(` (hooks not applicable for Claude.app agent mode)`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
if (report.targets.some((t) => t.kind === "desktop" && t.mcpServer === "added")) {
|
|
136
|
+
lines.push(
|
|
137
|
+
"Existing Claude.app agent-mode sessions snapshotted the previous config",
|
|
138
|
+
"and won't pick up this change automatically. Start a NEW agent-mode",
|
|
139
|
+
"session (not a resumed one) to use the updated kojee config.",
|
|
140
|
+
""
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
lines.push("To verify: in any new CC session, run /mcp and confirm `kojee` is listed.");
|
|
144
|
+
lines.push(" run /hooks and confirm both Stop and UserPromptSubmit show the kojee entries.");
|
|
145
|
+
lines.push("To remove: `kojee-mcp init --uninstall`");
|
|
146
|
+
return lines.join("\n");
|
|
147
|
+
}
|
|
148
|
+
function configureCodex(opts) {
|
|
149
|
+
const wh = resolveWizardWebhook(opts);
|
|
150
|
+
if (wh.error) {
|
|
151
|
+
return { runtime: "codex", output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
152
|
+
}
|
|
153
|
+
const url = wh.url || "https://YOUR-CODEX-WEBHOOK-RECEIVER.local/kojee";
|
|
154
|
+
const secret = wh.secret || generateWebhookSecret();
|
|
155
|
+
writeCodexConfig({
|
|
156
|
+
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
157
|
+
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {},
|
|
158
|
+
webhookUrl: url,
|
|
159
|
+
webhookSecret: secret
|
|
160
|
+
});
|
|
161
|
+
recordRuntime("codex");
|
|
162
|
+
const lines = [];
|
|
163
|
+
lines.push("Configured runtime: codex");
|
|
164
|
+
lines.push("Wake mode: webhook-sink + stop-hook peek (Codex has no channel injection).");
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push("Wrote [mcp_servers.kojee] to ~/.codex/config.toml:");
|
|
167
|
+
lines.push(indent(buildCodexMcpServerTable({ webhookUrl: url, webhookSecret: "<redacted>" })));
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("Wrote the Codex Stop hook (~/.codex/hooks.json; inline TOML form):");
|
|
170
|
+
lines.push(indent(buildCodexStopHookBlock()));
|
|
171
|
+
lines.push("");
|
|
172
|
+
lines.push(`webhook: ${wh.url ? wh.redactedSummary : "(receiver URL not set \u2014 fill KOJEE_WEBHOOK_URL above)"}`);
|
|
173
|
+
lines.push(`bounded-listen cap: ${CODEX_LISTEN_CAP_MS}ms (the model picks listen vs drain vs ignore).`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push("Next steps (codex):");
|
|
176
|
+
lines.push(" Restart Codex (or start a new `codex` / `codex exec` session) to load the MCP server and hook.");
|
|
177
|
+
lines.push(" Stand up your webhook receiver at KOJEE_WEBHOOK_URL \u2014 contract:");
|
|
178
|
+
lines.push(indent(buildWebhookReceiverNote()));
|
|
179
|
+
lines.push(" Verify: kojee-mcp doctor");
|
|
180
|
+
lines.push("");
|
|
181
|
+
lines.push(CODEX_UNVERIFIED_NOTE);
|
|
182
|
+
return { runtime: "codex", output: lines.join("\n"), exitCode: 0 };
|
|
183
|
+
}
|
|
184
|
+
function configureWebhookDaemon(runtime, opts) {
|
|
185
|
+
const wh = resolveWizardWebhook(opts);
|
|
186
|
+
if (wh.error) {
|
|
187
|
+
return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
188
|
+
}
|
|
189
|
+
recordRuntime(runtime);
|
|
190
|
+
const lines = [];
|
|
191
|
+
lines.push(`Configured runtime: ${runtime}`);
|
|
192
|
+
lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
|
|
193
|
+
lines.push("");
|
|
194
|
+
if (wh.url) {
|
|
195
|
+
const envFile = writeRuntimeEnvFile(runtime, wh.url, wh.secret);
|
|
196
|
+
lines.push("Export these before starting the daemon (also written to a source-able file):");
|
|
197
|
+
lines.push(` export KOJEE_RUNTIME="${runtime}"`);
|
|
198
|
+
lines.push(` export KOJEE_WEBHOOK_URL="${wh.url}"`);
|
|
199
|
+
lines.push(` export KOJEE_WEBHOOK_SECRET=<generated; in ${envFile}>`);
|
|
200
|
+
lines.push(` (validated: ${wh.redactedSummary})`);
|
|
201
|
+
lines.push(` source ${envFile}`);
|
|
202
|
+
} else {
|
|
203
|
+
lines.push("Set the daemon env (no receiver URL supplied yet):");
|
|
204
|
+
lines.push(` export KOJEE_RUNTIME="${runtime}"`);
|
|
205
|
+
lines.push(` export KOJEE_WEBHOOK_URL="https://YOUR-RECEIVER.local/kojee"`);
|
|
206
|
+
lines.push(` export KOJEE_WEBHOOK_SECRET=<generate a hex secret>`);
|
|
207
|
+
}
|
|
208
|
+
lines.push("");
|
|
209
|
+
lines.push("Receiver contract:");
|
|
210
|
+
lines.push(indent(buildWebhookReceiverNote()));
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push(`Next steps (${runtime}):`);
|
|
213
|
+
lines.push(" Start the daemon with the env above. Verify: kojee-mcp doctor (after the daemon is up).");
|
|
214
|
+
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
215
|
+
}
|
|
216
|
+
async function runWizardUninstall(runtime, opts) {
|
|
217
|
+
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
218
|
+
const lines = [`Uninstalling runtime: ${effective}`];
|
|
219
|
+
if (effective === "claude-code") {
|
|
220
|
+
const { runUninstall } = await import("./install-WBIUVBZW.js");
|
|
221
|
+
const report = runUninstall({
|
|
222
|
+
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
223
|
+
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
224
|
+
});
|
|
225
|
+
lines.push("Removing kojee from Claude:");
|
|
226
|
+
for (const t of report.targets) {
|
|
227
|
+
const label = t.kind === "cli" ? "CLI" : "Claude.app";
|
|
228
|
+
lines.push("");
|
|
229
|
+
lines.push(` ${t.path} (${label})`);
|
|
230
|
+
lines.push(` mcpServers.kojee ${t.mcpServer ? "\u2713 removed" : "\u2014 not found"}`);
|
|
231
|
+
if (t.kind === "cli") {
|
|
232
|
+
if (t.hooksPath) lines.push(` ${t.hooksPath} (hooks)`);
|
|
233
|
+
lines.push(` hook entries ${t.hooks ? "\u2713 removed" : "\u2014 not found"}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else if (effective === "codex") {
|
|
237
|
+
const removed = removeCodexConfig({
|
|
238
|
+
...opts.configPath ? { configPath: opts.configPath } : {},
|
|
239
|
+
...opts.hooksPath ? { hooksPath: opts.hooksPath } : {}
|
|
240
|
+
});
|
|
241
|
+
lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
|
|
242
|
+
lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
|
|
243
|
+
} else {
|
|
244
|
+
lines.push(" (hermes/openclaw write no MCP-config or hooks \u2014 nothing to tear down.");
|
|
245
|
+
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
246
|
+
const envPath = path.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
247
|
+
try {
|
|
248
|
+
fs.unlinkSync(envPath);
|
|
249
|
+
lines.push(` removed ${envPath}`);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
clearRuntimeRecord();
|
|
254
|
+
return { runtime: effective, output: lines.join("\n"), exitCode: 0 };
|
|
255
|
+
}
|
|
256
|
+
function indent(s) {
|
|
257
|
+
return s.split("\n").map((l) => " " + l).join("\n");
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
CODEX_UNVERIFIED_NOTE,
|
|
261
|
+
generateWebhookSecret,
|
|
262
|
+
resolveRuntime,
|
|
263
|
+
resolveWizardWebhook,
|
|
264
|
+
runWizard
|
|
265
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-E26AHU6J.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// src/tandem/recipe.ts
|
|
2
|
-
var SEND_BODY_PARAM = "body";
|
|
3
|
-
function buildMonitorCommand(logPath) {
|
|
4
|
-
return `npx kojee-mcp tail "${logPath}"`;
|
|
5
|
-
}
|
|
6
|
-
function buildMonitorSpawn(logPath) {
|
|
7
|
-
return `Monitor(command=\`${buildMonitorCommand(logPath)}\`, persistent=true, description="kojee Tandem events")`;
|
|
8
|
-
}
|
|
9
|
-
function buildReplyRecipe(event) {
|
|
10
|
-
if (event) {
|
|
11
|
-
return `reply directly with tandem_send(tandem_id="${event.tandem_id}", ${SEND_BODY_PARAM}="...", reply_to="${event.message_id}"); fetch context only if needed via tandem_messages(tandem_id, since=cursor-1)`;
|
|
12
|
-
}
|
|
13
|
-
return `reply directly with tandem_send(tandem_id, ${SEND_BODY_PARAM}, reply_to=<msg>); fetch context only if needed via tandem_messages(tandem_id, since=cursor-1)`;
|
|
14
|
-
}
|
|
15
|
-
function buildCatchUpNote() {
|
|
16
|
-
return "If the event line `cursor=<n>` ever jumps (a gap) or the log resets (the proxy caps and truncates the messages log in place on overflow \u2014 a `status=rotated` line is recorded in the status sibling), you may have missed messages: catch up with tandem_messages(tandem_id, since=<last cursor you saw>).";
|
|
17
|
-
}
|
|
18
|
-
function buildMonitorNudge(logPath) {
|
|
19
|
-
return `[kojee] Tandem events are being logged but no Monitor is reading them \u2014 you may be missing wake notifications. Spawn the watcher once: ${buildMonitorSpawn(logPath)}. Then ${buildReplyRecipe()}.`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export {
|
|
23
|
-
buildMonitorSpawn,
|
|
24
|
-
buildReplyRecipe,
|
|
25
|
-
buildCatchUpNote,
|
|
26
|
-
buildMonitorNudge
|
|
27
|
-
};
|