kojee-mcp 0.5.14 → 0.5.16
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-IMOEZ4NJ.js → chunk-34IRTWP6.js} +45 -14
- package/dist/{chunk-LVL25VLO.js → chunk-77HWBSRH.js} +0 -5
- package/dist/{chunk-3H3TL34J.js → chunk-IRD26KZG.js} +2 -2
- package/dist/cli.js +5 -5
- package/dist/{gateway-client-C6yx1mfM.d.ts → gateway-client-CbM2OC_w.d.ts} +19 -0
- package/dist/{hook-server-T2Z444OV.js → hook-server-XK2NHLJV.js} +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/{registry-TGALQP6M.js → registry-HPCRIWRF.js} +45 -1
- package/dist/{runtimes-CO43XUUK.js → runtimes-GG7EOFZH.js} +1 -3
- package/dist/{send-cli-RH7D4JDP.js → send-cli-T6RPZZQ4.js} +1 -1
- package/dist/{wizard-5ILBK6YD.js → wizard-3FDEWEYO.js} +177 -44
- package/package.json +1 -1
|
@@ -120,22 +120,10 @@ var GatewayClient = class {
|
|
|
120
120
|
return result ?? { content: [{ type: "text", text: "No result" }] };
|
|
121
121
|
}
|
|
122
122
|
async sendHttpRequest(rpcRequest, signal) {
|
|
123
|
-
const
|
|
124
|
-
this.privateKey,
|
|
125
|
-
this.kid,
|
|
126
|
-
"POST",
|
|
127
|
-
this.endpoint,
|
|
128
|
-
this.currentNonce,
|
|
129
|
-
this.token
|
|
130
|
-
);
|
|
123
|
+
const headers = await this.buildAuthHeaders("POST", this.endpoint);
|
|
131
124
|
return fetch(this.endpoint, {
|
|
132
125
|
method: "POST",
|
|
133
|
-
headers
|
|
134
|
-
"Content-Type": "application/json",
|
|
135
|
-
Authorization: `DPoP ${this.token}`,
|
|
136
|
-
DPoP: proof,
|
|
137
|
-
"Mcp-Session-Id": getSessionId()
|
|
138
|
-
},
|
|
126
|
+
headers,
|
|
139
127
|
body: JSON.stringify(rpcRequest),
|
|
140
128
|
// ROUND-3 MAJOR A: the caller's AbortSignal rides HERE (a real fetch
|
|
141
129
|
// option), never inside the JSON-RPC body. `undefined` is a valid value
|
|
@@ -143,6 +131,49 @@ var GatewayClient = class {
|
|
|
143
131
|
...signal ? { signal } : {}
|
|
144
132
|
});
|
|
145
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Build the DPoP-authed request headers shared by every backend call: a fresh
|
|
136
|
+
* DPoP proof bound to (method, url) + the current nonce + the access token, the
|
|
137
|
+
* `Authorization: DPoP <token>` header, the stable `Mcp-Session-Id`, and the
|
|
138
|
+
* JSON content type. Factored out of sendHttpRequest so the REST helper below
|
|
139
|
+
* reuses the EXACT same auth construction (no auth-path divergence).
|
|
140
|
+
*/
|
|
141
|
+
async buildAuthHeaders(method, url) {
|
|
142
|
+
const proof = await createDPoPProof(
|
|
143
|
+
this.privateKey,
|
|
144
|
+
this.kid,
|
|
145
|
+
method,
|
|
146
|
+
url,
|
|
147
|
+
this.currentNonce,
|
|
148
|
+
this.token
|
|
149
|
+
);
|
|
150
|
+
return {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
Authorization: `DPoP ${this.token}`,
|
|
153
|
+
DPoP: proof,
|
|
154
|
+
"Mcp-Session-Id": getSessionId()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send an authed REST request (NOT JSON-RPC) to `<brokerUrl><path>`, reusing
|
|
159
|
+
* the SAME DPoP-proof + Authorization + Mcp-Session-Id header construction as
|
|
160
|
+
* the JSON-RPC path (buildAuthHeaders). Returns the RAW `Response` — callers
|
|
161
|
+
* own status handling; there is deliberately NO error-translation here, so a
|
|
162
|
+
* best-effort caller (presence) can fire-and-forget and swallow non-2xx.
|
|
163
|
+
*
|
|
164
|
+
* `body` is JSON-serialized when present (omitted otherwise). `signal` rides
|
|
165
|
+
* the real fetch option (per the JSON-RPC path's ROUND-3 convention).
|
|
166
|
+
*/
|
|
167
|
+
async sendRest(method, path, body, signal) {
|
|
168
|
+
const url = `${this.brokerUrl}${path}`;
|
|
169
|
+
const headers = await this.buildAuthHeaders(method, url);
|
|
170
|
+
return fetch(url, {
|
|
171
|
+
method,
|
|
172
|
+
headers,
|
|
173
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
174
|
+
...signal ? { signal } : {}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
146
177
|
trackNonce(response) {
|
|
147
178
|
const nonce = response.headers.get("DPoP-Nonce");
|
|
148
179
|
if (nonce) {
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
// src/wizard/runtimes.ts
|
|
2
2
|
var WIZARD_RUNTIMES = ["claude-code", "hermes", "openclaw", "codex"];
|
|
3
|
-
var WEBHOOK_RUNTIMES = /* @__PURE__ */ new Set([
|
|
4
|
-
"hermes",
|
|
5
|
-
"openclaw"
|
|
6
|
-
]);
|
|
7
3
|
function isWizardRuntime(value) {
|
|
8
4
|
return WIZARD_RUNTIMES.includes(value);
|
|
9
5
|
}
|
|
@@ -16,7 +12,6 @@ var RUNTIME_MENU = [
|
|
|
16
12
|
|
|
17
13
|
export {
|
|
18
14
|
WIZARD_RUNTIMES,
|
|
19
|
-
WEBHOOK_RUNTIMES,
|
|
20
15
|
isWizardRuntime,
|
|
21
16
|
RUNTIME_MENU
|
|
22
17
|
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
GatewayClient,
|
|
6
6
|
applyStableSessionId
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-34IRTWP6.js";
|
|
8
8
|
import {
|
|
9
9
|
AuthModule
|
|
10
10
|
} from "./chunk-JXMVZEQ7.js";
|
|
@@ -312,7 +312,7 @@ async function startProxy(config) {
|
|
|
312
312
|
}
|
|
313
313
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
314
314
|
let server;
|
|
315
|
-
const { selectDelivery } = await import("./registry-
|
|
315
|
+
const { selectDelivery } = await import("./registry-HPCRIWRF.js");
|
|
316
316
|
const delivery = selectDelivery(adapter.runtime, {
|
|
317
317
|
supportsChannels: adapter.supportsChannels
|
|
318
318
|
});
|
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-OGHDTFAX.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IRD26KZG.js";
|
|
8
8
|
import "./chunk-XXFVWP6H.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
11
11
|
} from "./chunk-YH27B6SW.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-34IRTWP6.js";
|
|
13
13
|
import "./chunk-JXMVZEQ7.js";
|
|
14
14
|
import "./chunk-NR4Y54OL.js";
|
|
15
15
|
import {
|
|
@@ -79,7 +79,7 @@ Restart Claude Code for hooks to take effect.`
|
|
|
79
79
|
program.command("send <tandem_id>").description(
|
|
80
80
|
"Send a Tandem message using this machine's paired credentials (~/.kojee). Prints one JSON envelope to stdout: {ok, message_id, cursor, text} on success, {ok:false, error:<typed code>, message} on failure (exit 1)."
|
|
81
81
|
).requiredOption("--body <text>", "Message body (required)").option("--reply-to <message_id>", "Message id this send replies to").option("--kind <kind>", "Message kind: message | status (default: backend default)").action(async (tandemId, opts) => {
|
|
82
|
-
const { runSendCli } = await import("./send-cli-
|
|
82
|
+
const { runSendCli } = await import("./send-cli-T6RPZZQ4.js");
|
|
83
83
|
const { exitCode, envelope } = await runSendCli({
|
|
84
84
|
tandemId,
|
|
85
85
|
body: opts.body,
|
|
@@ -140,7 +140,7 @@ program.command("init").description(
|
|
|
140
140
|
console.error("Not paired. Run `kojee-mcp pair <code> --url <broker>` first, then re-run `init` \u2014 or pass --token/--pair-code, or run `init` in a terminal for the guided wizard.");
|
|
141
141
|
process.exit(1);
|
|
142
142
|
}
|
|
143
|
-
const { runWizard } = await import("./wizard-
|
|
143
|
+
const { runWizard } = await import("./wizard-3FDEWEYO.js");
|
|
144
144
|
const result = await runWizard({
|
|
145
145
|
...opts.runtime !== void 0 ? { runtime: opts.runtime } : {},
|
|
146
146
|
...opts.uninstall ? { uninstall: true } : {},
|
|
@@ -178,7 +178,7 @@ async function withReadline(fn) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
async function promptRuntimeFromTty() {
|
|
181
|
-
const { RUNTIME_MENU } = await import("./runtimes-
|
|
181
|
+
const { RUNTIME_MENU } = await import("./runtimes-GG7EOFZH.js");
|
|
182
182
|
return withReadline(async (ask) => {
|
|
183
183
|
const menu = RUNTIME_MENU.map((m) => `[${m.index}] ${m.runtime}`).join(" ");
|
|
184
184
|
const answer = (await ask(`Which runtime is this proxy for? ${menu}
|
|
@@ -90,6 +90,25 @@ declare class GatewayClient {
|
|
|
90
90
|
sendRpc(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<ToolCallResult>;
|
|
91
91
|
private executeWithRetries;
|
|
92
92
|
private sendHttpRequest;
|
|
93
|
+
/**
|
|
94
|
+
* Build the DPoP-authed request headers shared by every backend call: a fresh
|
|
95
|
+
* DPoP proof bound to (method, url) + the current nonce + the access token, the
|
|
96
|
+
* `Authorization: DPoP <token>` header, the stable `Mcp-Session-Id`, and the
|
|
97
|
+
* JSON content type. Factored out of sendHttpRequest so the REST helper below
|
|
98
|
+
* reuses the EXACT same auth construction (no auth-path divergence).
|
|
99
|
+
*/
|
|
100
|
+
private buildAuthHeaders;
|
|
101
|
+
/**
|
|
102
|
+
* Send an authed REST request (NOT JSON-RPC) to `<brokerUrl><path>`, reusing
|
|
103
|
+
* the SAME DPoP-proof + Authorization + Mcp-Session-Id header construction as
|
|
104
|
+
* the JSON-RPC path (buildAuthHeaders). Returns the RAW `Response` — callers
|
|
105
|
+
* own status handling; there is deliberately NO error-translation here, so a
|
|
106
|
+
* best-effort caller (presence) can fire-and-forget and swallow non-2xx.
|
|
107
|
+
*
|
|
108
|
+
* `body` is JSON-serialized when present (omitted otherwise). `signal` rides
|
|
109
|
+
* the real fetch option (per the JSON-RPC path's ROUND-3 convention).
|
|
110
|
+
*/
|
|
111
|
+
sendRest(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
|
|
93
112
|
private trackNonce;
|
|
94
113
|
private tryParseErrorBody;
|
|
95
114
|
}
|
|
@@ -45,6 +45,7 @@ async function handleRequest(req, res, opts) {
|
|
|
45
45
|
if (req.method === "GET" && url.pathname === "/poll") {
|
|
46
46
|
const type = url.searchParams.get("type") ?? "";
|
|
47
47
|
const timeoutMs = Number.parseInt(url.searchParams.get("timeout_ms") ?? "0", 10);
|
|
48
|
+
fireLiveliness(opts.liveliness, type);
|
|
48
49
|
if (type === "user-prompt-submit") {
|
|
49
50
|
return respondWithEvents(res, opts);
|
|
50
51
|
}
|
|
@@ -131,6 +132,17 @@ function readBody(req, maxBytes) {
|
|
|
131
132
|
req.on("error", reject);
|
|
132
133
|
});
|
|
133
134
|
}
|
|
135
|
+
function fireLiveliness(liveliness, type) {
|
|
136
|
+
if (!liveliness) return;
|
|
137
|
+
try {
|
|
138
|
+
if (type === "user-prompt-submit") {
|
|
139
|
+
liveliness.onTurnStart();
|
|
140
|
+
} else if (type === "stop") {
|
|
141
|
+
liveliness.onTurnEnd();
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
134
146
|
function respondWithEvents(res, opts) {
|
|
135
147
|
const entries = opts.queue.takeForHook();
|
|
136
148
|
const events = entries.map((entry) => opts.adapter.formatTandemEvent(entry.event));
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listTandemIds,
|
|
3
3
|
startProxy
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IRD26KZG.js";
|
|
5
5
|
import "./chunk-XXFVWP6H.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-34IRTWP6.js";
|
|
7
7
|
import "./chunk-JXMVZEQ7.js";
|
|
8
8
|
import "./chunk-NR4Y54OL.js";
|
|
9
9
|
import "./chunk-CH32ELFX.js";
|
package/dist/lib.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { KeyLike, JWK } from 'jose';
|
|
2
|
-
import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient, M as McpToolDefinition, T as ToolCallResult, P as ProxyConfig } from './gateway-client-
|
|
2
|
+
import { L as LoadedKeyPair, K as KeystoreData, G as GatewayClient, M as McpToolDefinition, T as ToolCallResult, P as ProxyConfig } from './gateway-client-CbM2OC_w.js';
|
|
3
3
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/lib.js
CHANGED
|
@@ -43,6 +43,40 @@ function formatChannelNotification(event) {
|
|
|
43
43
|
return claudeCodeAdapter.formatTandemEvent(event);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// src/delivery/liveliness.ts
|
|
47
|
+
function createPresenceProvider({
|
|
48
|
+
post,
|
|
49
|
+
debounceMs = 2e3
|
|
50
|
+
}) {
|
|
51
|
+
let idleTimer = null;
|
|
52
|
+
const clearIdleTimer = () => {
|
|
53
|
+
if (idleTimer !== null) {
|
|
54
|
+
clearTimeout(idleTimer);
|
|
55
|
+
idleTimer = null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const emit = (state) => {
|
|
59
|
+
void post(state).catch(() => {
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
onTurnStart() {
|
|
64
|
+
clearIdleTimer();
|
|
65
|
+
emit("working");
|
|
66
|
+
},
|
|
67
|
+
onTurnEnd() {
|
|
68
|
+
clearIdleTimer();
|
|
69
|
+
idleTimer = setTimeout(() => {
|
|
70
|
+
idleTimer = null;
|
|
71
|
+
emit("idle");
|
|
72
|
+
}, debounceMs);
|
|
73
|
+
},
|
|
74
|
+
stop() {
|
|
75
|
+
clearIdleTimer();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
46
80
|
// src/delivery/claude-code.ts
|
|
47
81
|
function createClaudeCodeDelivery() {
|
|
48
82
|
let eventLog = null;
|
|
@@ -52,11 +86,12 @@ function createClaudeCodeDelivery() {
|
|
|
52
86
|
let hookServer = null;
|
|
53
87
|
let cleanupDiscoveryFile = null;
|
|
54
88
|
let streamHandle = null;
|
|
89
|
+
let liveliness = null;
|
|
55
90
|
return {
|
|
56
91
|
name: "claude-code",
|
|
57
92
|
async start(ctx) {
|
|
58
93
|
const { EventQueue } = await import("./event-queue-5YVJFR3E.js");
|
|
59
|
-
const { startHookServer } = await import("./hook-server-
|
|
94
|
+
const { startHookServer } = await import("./hook-server-XK2NHLJV.js");
|
|
60
95
|
const {
|
|
61
96
|
writeDiscoveryByKey,
|
|
62
97
|
cleanupDiscoveryByKey,
|
|
@@ -117,6 +152,9 @@ function createClaudeCodeDelivery() {
|
|
|
117
152
|
err.message
|
|
118
153
|
);
|
|
119
154
|
}
|
|
155
|
+
liveliness = createPresenceProvider({
|
|
156
|
+
post: (state) => ctx.gateway.sendRest("POST", "/api/v2/presence", { state })
|
|
157
|
+
});
|
|
120
158
|
queue = new EventQueue();
|
|
121
159
|
const localQueue = queue;
|
|
122
160
|
let liveStream = null;
|
|
@@ -124,6 +162,7 @@ function createClaudeCodeDelivery() {
|
|
|
124
162
|
port: 0,
|
|
125
163
|
queue: localQueue,
|
|
126
164
|
adapter: ctx.adapter,
|
|
165
|
+
liveliness,
|
|
127
166
|
...controlToken !== null ? { controlToken, send: { gateway: ctx.gateway, authToken: controlToken } } : {},
|
|
128
167
|
getStreamState: () => liveStream ? liveStream.getState() : {
|
|
129
168
|
connected: false,
|
|
@@ -152,11 +191,15 @@ function createClaudeCodeDelivery() {
|
|
|
152
191
|
cleanupDiscoveryFile = () => cleanupDiscoveryByKey(discoveryKey);
|
|
153
192
|
const localCleanupDiscovery = cleanupDiscoveryFile;
|
|
154
193
|
const localHookServer = hookServer;
|
|
194
|
+
const localLiveliness = liveliness;
|
|
155
195
|
const exitCleanup = () => {
|
|
156
196
|
localCleanupDiscovery();
|
|
157
197
|
eventLog?.cleanup();
|
|
158
198
|
};
|
|
159
199
|
const teardown = [
|
|
200
|
+
() => {
|
|
201
|
+
localLiveliness.stop();
|
|
202
|
+
},
|
|
160
203
|
() => {
|
|
161
204
|
void webhookSink?.stop();
|
|
162
205
|
},
|
|
@@ -207,6 +250,7 @@ function createClaudeCodeDelivery() {
|
|
|
207
250
|
});
|
|
208
251
|
},
|
|
209
252
|
async stop() {
|
|
253
|
+
liveliness?.stop();
|
|
210
254
|
await webhookSink?.stop();
|
|
211
255
|
cleanupDiscoveryFile?.();
|
|
212
256
|
eventLog?.cleanup();
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
WIZARD_RUNTIMES,
|
|
17
17
|
isWizardRuntime
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-77HWBSRH.js";
|
|
19
19
|
import {
|
|
20
20
|
resolveSignatureEmission,
|
|
21
21
|
resolveWebhookConfig
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
|
|
31
31
|
// src/wizard/wizard.ts
|
|
32
32
|
import crypto2 from "crypto";
|
|
33
|
-
import
|
|
34
|
-
import
|
|
33
|
+
import fs5 from "fs";
|
|
34
|
+
import path6 from "path";
|
|
35
35
|
import { fileURLToPath } from "url";
|
|
36
36
|
|
|
37
37
|
// src/wizard/registry.ts
|
|
@@ -318,6 +318,141 @@ function installHermes(inp) {
|
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// src/wizard/capabilities/openclaw-channel-config.ts
|
|
322
|
+
import fs4 from "fs";
|
|
323
|
+
import path5 from "path";
|
|
324
|
+
var CHANNEL_ID = "kojee-tandem";
|
|
325
|
+
function mergeOpenclawChannelConfig(existing, block) {
|
|
326
|
+
const prevChannels = existing.channels && typeof existing.channels === "object" ? existing.channels : {};
|
|
327
|
+
return {
|
|
328
|
+
...existing,
|
|
329
|
+
channels: {
|
|
330
|
+
...prevChannels,
|
|
331
|
+
[CHANNEL_ID]: { ...block }
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function readOpenclawConfigState(configPath) {
|
|
336
|
+
let raw;
|
|
337
|
+
try {
|
|
338
|
+
raw = fs4.readFileSync(configPath, "utf8");
|
|
339
|
+
} catch {
|
|
340
|
+
return { cfg: {}, unparseable: false };
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
const parsed = JSON.parse(raw);
|
|
344
|
+
return {
|
|
345
|
+
cfg: parsed && typeof parsed === "object" ? parsed : {},
|
|
346
|
+
unparseable: false
|
|
347
|
+
};
|
|
348
|
+
} catch {
|
|
349
|
+
return { cfg: {}, unparseable: true };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function atomicWrite(filePath, content, secret) {
|
|
353
|
+
fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
354
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
355
|
+
fs4.writeFileSync(tmp, content, { ...secret ? { mode: 384 } : {} });
|
|
356
|
+
if (secret) secureFile(tmp);
|
|
357
|
+
fs4.renameSync(tmp, filePath);
|
|
358
|
+
if (secret) secureFile(filePath);
|
|
359
|
+
}
|
|
360
|
+
function writeOpenclawChannelConfig(configPath, block, opts = {}) {
|
|
361
|
+
const { cfg, unparseable } = readOpenclawConfigState(configPath);
|
|
362
|
+
let backedUp;
|
|
363
|
+
if (unparseable) {
|
|
364
|
+
const stamp = opts.timestamp ?? corruptStamp();
|
|
365
|
+
backedUp = `${configPath}.corrupt-${stamp}`;
|
|
366
|
+
fs4.copyFileSync(configPath, backedUp);
|
|
367
|
+
}
|
|
368
|
+
const merged = mergeOpenclawChannelConfig(cfg, block);
|
|
369
|
+
atomicWrite(configPath, JSON.stringify(merged, null, 2) + "\n", Boolean(block.credential));
|
|
370
|
+
return backedUp ? { backedUp } : {};
|
|
371
|
+
}
|
|
372
|
+
function corruptStamp() {
|
|
373
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
374
|
+
}
|
|
375
|
+
function removeOpenclawChannel(configPath) {
|
|
376
|
+
let raw;
|
|
377
|
+
try {
|
|
378
|
+
raw = fs4.readFileSync(configPath, "utf8");
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
let cfg;
|
|
383
|
+
try {
|
|
384
|
+
cfg = JSON.parse(raw);
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const channels = cfg.channels && typeof cfg.channels === "object" ? cfg.channels : void 0;
|
|
389
|
+
if (!channels || !(CHANNEL_ID in channels)) return false;
|
|
390
|
+
const { [CHANNEL_ID]: _removed, ...rest } = channels;
|
|
391
|
+
const next = { ...cfg, channels: rest };
|
|
392
|
+
atomicWrite(configPath, JSON.stringify(next, null, 2) + "\n", false);
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/wizard/installers/openclaw.ts
|
|
397
|
+
function installOpenclaw(inp) {
|
|
398
|
+
const gatewayUrl = inp.url ? inp.url.replace(/\/+$/, "") : void 0;
|
|
399
|
+
const block = {
|
|
400
|
+
enabled: true,
|
|
401
|
+
...gatewayUrl ? { gatewayUrl } : {},
|
|
402
|
+
// Token-mode → credential lands here (config-first). Paired-mode → omitted.
|
|
403
|
+
...inp.token ? { credential: inp.token } : {},
|
|
404
|
+
tandems: [],
|
|
405
|
+
selfPrincipals: [],
|
|
406
|
+
wake: { severities: [], mentionsOnly: false }
|
|
407
|
+
};
|
|
408
|
+
const stamp = (inp.now ? inp.now() : /* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
409
|
+
const { backedUp } = writeOpenclawChannelConfig(inp.openclawConfigPath, block, { timestamp: stamp });
|
|
410
|
+
const installCmd = inp.pluginSourceDir ? `openclaw plugins install -l ${inp.pluginSourceDir}` : `openclaw plugins install npm:openclaw-channel-kojee-tandem (available once published)`;
|
|
411
|
+
const lines = [];
|
|
412
|
+
if (backedUp) {
|
|
413
|
+
lines.push(
|
|
414
|
+
`WARNING: ${inp.openclawConfigPath} was present but could not be parsed as JSON.`,
|
|
415
|
+
` The original was backed up to ${backedUp} before writing the kojee-tandem block.`,
|
|
416
|
+
` If it held other channels, recover them from that backup and re-merge by hand.`,
|
|
417
|
+
""
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
lines.push(
|
|
421
|
+
"Configured runtime: openclaw (in-process channel plugin)",
|
|
422
|
+
"Wake mode: native OpenClaw channel \u2014 the plugin streams Tandem events while the gateway is up.",
|
|
423
|
+
` channel config: ${inp.openclawConfigPath} (channels.${CHANNEL_ID})`,
|
|
424
|
+
inp.token ? ` credential: written into the channel block (owner-only perms; the plugin's config-first path)` : ` credential: shared ~/.kojee/config.json (paired) \u2014 not duplicated into the channel block`,
|
|
425
|
+
"",
|
|
426
|
+
"Next steps (openclaw):",
|
|
427
|
+
` - Install the plugin via OpenClaw's own plugin manager: ${installCmd}`,
|
|
428
|
+
" - Reload the gateway to load the plugin: openclaw gateway restart",
|
|
429
|
+
" - Verify: openclaw plugins inspect kojee-tandem / openclaw channels status"
|
|
430
|
+
);
|
|
431
|
+
return {
|
|
432
|
+
runtime: "openclaw",
|
|
433
|
+
output: lines.join("\n"),
|
|
434
|
+
exitCode: 0,
|
|
435
|
+
configPath: inp.openclawConfigPath
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function uninstallOpenclaw(inp) {
|
|
439
|
+
const removed = removeOpenclawChannel(inp.openclawConfigPath);
|
|
440
|
+
const lines = ["Uninstalling runtime: openclaw (in-process channel plugin)"];
|
|
441
|
+
lines.push(
|
|
442
|
+
removed ? ` removed channels.${CHANNEL_ID} from ${inp.openclawConfigPath} (sibling channels preserved)` : ` no channels.${CHANNEL_ID} block found in ${inp.openclawConfigPath} \u2014 nothing to remove`
|
|
443
|
+
);
|
|
444
|
+
lines.push("");
|
|
445
|
+
lines.push("Next steps (openclaw):");
|
|
446
|
+
lines.push(" - Remove the plugin via OpenClaw's own plugin manager: openclaw plugins uninstall kojee-tandem");
|
|
447
|
+
lines.push(" - Reload the gateway: openclaw gateway restart");
|
|
448
|
+
return {
|
|
449
|
+
runtime: "openclaw",
|
|
450
|
+
output: lines.join("\n"),
|
|
451
|
+
exitCode: 0,
|
|
452
|
+
configPath: inp.openclawConfigPath
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
321
456
|
// src/wizard/wizard.ts
|
|
322
457
|
var DEFAULT_BROKER_URL = "https://rosie-staging.kojee.net";
|
|
323
458
|
function generateWebhookSecret() {
|
|
@@ -383,22 +518,6 @@ function resolveWizardWebhook(opts) {
|
|
|
383
518
|
...warning !== void 0 ? { warning } : {}
|
|
384
519
|
};
|
|
385
520
|
}
|
|
386
|
-
function writeRuntimeEnvFile(runtime, url, secret, signatureEnv = []) {
|
|
387
|
-
const envPath = path5.join(kojeeHomeDir(), ".kojee", `${runtime}.env`);
|
|
388
|
-
const body = [
|
|
389
|
-
`# kojee daemon env for runtime=${runtime} (source this before starting the daemon)`,
|
|
390
|
-
`export KOJEE_RUNTIME=${shellSingleQuote(runtime)}`,
|
|
391
|
-
`export KOJEE_WEBHOOK_URL=${shellSingleQuote(url)}`,
|
|
392
|
-
`export KOJEE_WEBHOOK_SECRET=${shellSingleQuote(secret)}`,
|
|
393
|
-
// Signature emission overrides (0.5.3) — only when explicitly configured.
|
|
394
|
-
...signatureEnv.map(([k, v]) => `export ${k}=${shellSingleQuote(v)}`),
|
|
395
|
-
""
|
|
396
|
-
].join("\n");
|
|
397
|
-
fs4.mkdirSync(path5.dirname(envPath), { recursive: true, mode: 448 });
|
|
398
|
-
fs4.writeFileSync(envPath, body, { mode: 384 });
|
|
399
|
-
secureFile(envPath);
|
|
400
|
-
return envPath;
|
|
401
|
-
}
|
|
402
521
|
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.";
|
|
403
522
|
async function gatherGuidedInputs(runtime, opts) {
|
|
404
523
|
const preamble = [];
|
|
@@ -599,26 +718,8 @@ function buildDaemonEnvBlock(runtime, wh, envFile) {
|
|
|
599
718
|
lines.push(indent(buildWebhookReceiverNote({ header: wh.signatureHeader, prefix: wh.signaturePrefix })));
|
|
600
719
|
return lines;
|
|
601
720
|
}
|
|
602
|
-
function configureWebhookDaemon(runtime, opts) {
|
|
603
|
-
const wh = resolveWizardWebhook(opts);
|
|
604
|
-
if (wh.error) {
|
|
605
|
-
return { runtime, output: `webhook env ERROR: ${wh.error}`, exitCode: 2 };
|
|
606
|
-
}
|
|
607
|
-
recordRuntime(runtime);
|
|
608
|
-
const lines = [];
|
|
609
|
-
lines.push(`Configured runtime: ${runtime}`);
|
|
610
|
-
lines.push("Wake mode: webhook sink (daemon-consumed). NO MCP-config file, NO hooks written.");
|
|
611
|
-
lines.push("");
|
|
612
|
-
if (wh.warning) lines.push(`webhook WARNING: ${wh.warning}`);
|
|
613
|
-
const envFile = wh.url ? writeRuntimeEnvFile(runtime, wh.url, wh.secret, wh.signatureEnv) : void 0;
|
|
614
|
-
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
615
|
-
lines.push("");
|
|
616
|
-
lines.push(`Next steps (${runtime}):`);
|
|
617
|
-
lines.push(" Start the daemon with the env above. Verify: kojee-mcp doctor (after the daemon is up).");
|
|
618
|
-
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
619
|
-
}
|
|
620
721
|
function distDir() {
|
|
621
|
-
return
|
|
722
|
+
return path6.dirname(fileURLToPath(import.meta.url));
|
|
622
723
|
}
|
|
623
724
|
function resolveBinPath() {
|
|
624
725
|
const entry = process.argv[1];
|
|
@@ -663,7 +764,7 @@ function configureHermes(opts) {
|
|
|
663
764
|
return { runtime, output: lines.join("\n"), exitCode: install.exitCode };
|
|
664
765
|
}
|
|
665
766
|
recordRuntime(runtime);
|
|
666
|
-
const envFile =
|
|
767
|
+
const envFile = path6.join(home, ".kojee", "hermes.env");
|
|
667
768
|
lines.push(...buildDaemonEnvBlock(runtime, wh, envFile));
|
|
668
769
|
lines.push("");
|
|
669
770
|
lines.push(install.output);
|
|
@@ -678,6 +779,35 @@ function configureHermes(opts) {
|
|
|
678
779
|
lines.push(" Verify: kojee-mcp doctor (after the daemon is up).");
|
|
679
780
|
return { runtime, output: lines.join("\n"), exitCode: 0 };
|
|
680
781
|
}
|
|
782
|
+
function openclawConfigPath(opts) {
|
|
783
|
+
return opts.openclawConfigPath ?? path6.join(kojeeHomeDir(), ".openclaw", "config.json");
|
|
784
|
+
}
|
|
785
|
+
function resolveOpenclawPluginSourceDir() {
|
|
786
|
+
const candidates = [
|
|
787
|
+
path6.resolve(distDir(), "..", "..", "integrations", "openclaw-plugin"),
|
|
788
|
+
path6.resolve(distDir(), "..", "..", "..", "integrations", "openclaw-plugin")
|
|
789
|
+
];
|
|
790
|
+
for (const dir of candidates) {
|
|
791
|
+
if (fs5.existsSync(path6.join(dir, "openclaw.plugin.json"))) return dir;
|
|
792
|
+
}
|
|
793
|
+
return void 0;
|
|
794
|
+
}
|
|
795
|
+
function configureOpenclaw(opts) {
|
|
796
|
+
const runtime = "openclaw";
|
|
797
|
+
const env = opts.env ?? process.env;
|
|
798
|
+
const token = (opts.token ?? "").trim();
|
|
799
|
+
const url = (opts.url ?? env["KOJEE_GATEWAY_URL"] ?? "").trim();
|
|
800
|
+
const pluginSourceDir = resolveOpenclawPluginSourceDir();
|
|
801
|
+
const install = installOpenclaw({
|
|
802
|
+
homeDir: kojeeHomeDir(),
|
|
803
|
+
openclawConfigPath: openclawConfigPath(opts),
|
|
804
|
+
...url ? { url } : {},
|
|
805
|
+
...token ? { token } : {},
|
|
806
|
+
...pluginSourceDir ? { pluginSourceDir } : {}
|
|
807
|
+
});
|
|
808
|
+
recordRuntime(runtime);
|
|
809
|
+
return { runtime, output: install.output, exitCode: install.exitCode };
|
|
810
|
+
}
|
|
681
811
|
async function runWizardUninstall(runtime, opts) {
|
|
682
812
|
const effective = opts.runtime !== void 0 ? runtime : readRecordedRuntime() ?? runtime;
|
|
683
813
|
const lines = [`Uninstalling runtime: ${effective}`];
|
|
@@ -705,12 +835,15 @@ async function runWizardUninstall(runtime, opts) {
|
|
|
705
835
|
});
|
|
706
836
|
lines.push(` config.toml [mcp_servers.kojee]: ${removed.mcpServer ? "removed" : "not found"}`);
|
|
707
837
|
lines.push(` hooks.json Stop: ${removed.stopHook ? "removed" : "not found"}`);
|
|
838
|
+
} else if (effective === "openclaw") {
|
|
839
|
+
const un = uninstallOpenclaw({ openclawConfigPath: openclawConfigPath(opts) });
|
|
840
|
+
lines.push(un.output);
|
|
708
841
|
} else {
|
|
709
|
-
lines.push(" (hermes
|
|
842
|
+
lines.push(" (hermes writes no MCP-config or hooks \u2014 nothing to tear down.");
|
|
710
843
|
lines.push(" Stop the daemon and unset KOJEE_WEBHOOK_URL/SECRET to disable the sink.)");
|
|
711
|
-
const envPath =
|
|
844
|
+
const envPath = path6.join(kojeeHomeDir(), ".kojee", `${effective}.env`);
|
|
712
845
|
try {
|
|
713
|
-
|
|
846
|
+
fs5.unlinkSync(envPath);
|
|
714
847
|
lines.push(` removed ${envPath}`);
|
|
715
848
|
} catch {
|
|
716
849
|
}
|
|
@@ -758,8 +891,8 @@ registerBuiltinInstaller(
|
|
|
758
891
|
registerBuiltinInstaller(
|
|
759
892
|
"openclaw",
|
|
760
893
|
"OpenClaw",
|
|
761
|
-
["pair-credential", "
|
|
762
|
-
(o) =>
|
|
894
|
+
["pair-credential", "in-process-plugin"],
|
|
895
|
+
(o) => configureOpenclaw(o)
|
|
763
896
|
);
|
|
764
897
|
export {
|
|
765
898
|
CODEX_UNVERIFIED_NOTE,
|