kojee-mcp 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/{chunk-36DMIXH7.js → chunk-BJMASMKX.js} +13 -23
- package/dist/chunk-E26AHU6J.js +27 -0
- package/dist/{chunk-E7TE4QZD.js → chunk-GBOTBYEP.js} +2 -1
- package/dist/{chunk-ZGVUM4AG.js → chunk-LCFCCWMM.js} +157 -257
- package/dist/{chunk-WHTH6WBP.js → chunk-LSUB6QMP.js} +3 -0
- package/dist/chunk-QB22PD6T.js +358 -0
- package/dist/chunk-VLZADEFC.js +247 -0
- package/dist/{chunk-VZVGTHGF.js → chunk-W6YRLSD4.js} +2 -1
- package/dist/cli.js +29 -11
- package/dist/doctor-GILTOH2R.js +222 -0
- package/dist/event-log-R6VW6GAF.js +17 -0
- package/dist/{hook-server-43QS7L7P.js → hook-server-QF5JVUHV.js} +28 -0
- package/dist/index.js +4 -2
- package/dist/{install-WV25CRU2.js → install-D2HIPOMT.js} +4 -3
- package/dist/{paired-config-OAR3O3XY.js → paired-config-RB4SABOS.js} +1 -1
- package/dist/resubscribe-SLZNA76S.js +59 -0
- package/dist/{session-discovery-WSHLR4OV.js → session-discovery-QE5TTAPS.js} +1 -1
- package/dist/stop-hook-VLQS6QPR.js +118 -0
- package/dist/tail-stream-UZ42UIWO.js +161 -0
- package/dist/{user-prompt-submit-hook-WSRIJVF4.js → user-prompt-submit-hook-C42DPDBO.js} +4 -4
- package/package.json +9 -7
- package/dist/event-log-ETWR6PPY.js +0 -112
- package/dist/stop-hook-5XU3EQAE.js +0 -76
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# kojee-mcp
|
|
2
2
|
|
|
3
|
+
> **First time using Kojee Tandem in Claude Code?** See [docs/getting-started-tandem.md](docs/getting-started-tandem.md) for the 3-command setup + verification walkthrough.
|
|
4
|
+
|
|
3
5
|
There are two ways to connect Kojee to an MCP-capable agent:
|
|
4
6
|
|
|
5
7
|
1. **Mobile, web & desktop (recommended)** — paste the Kojee MCP URL into the app's "Add custom connector" dialog. The app handles OAuth login and consent. No local install. Works on Claude (web/desktop/iOS/Android) and ChatGPT (web/desktop/iOS/Android with Developer Mode enabled).
|
|
@@ -126,26 +128,35 @@ If both Channels (dangerous-flag launched) and hooks are active, the proxy dedup
|
|
|
126
128
|
|
|
127
129
|
## Waiting on Tandem Peers
|
|
128
130
|
|
|
129
|
-
When kojee-mcp is running under Claude Code, the proxy writes one line per
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
When kojee-mcp is running under Claude Code, the proxy writes one line per
|
|
132
|
+
Tandem message to a per-session log file under the OS's temp directory
|
|
133
|
+
(`os.tmpdir()` — `/tmp` on Linux, `/var/folders/…` on macOS, `%TEMP%` on
|
|
134
|
+
Windows). The agent watches this log via Claude Code's built-in `Monitor`
|
|
135
|
+
tool — spawned once at the start of each session:
|
|
132
136
|
|
|
133
137
|
```ts
|
|
134
138
|
Monitor({
|
|
135
|
-
command:
|
|
139
|
+
command: `npx kojee-mcp tail "${eventLogPath}"`,
|
|
136
140
|
persistent: true,
|
|
137
141
|
description: "kojee Tandem events",
|
|
138
142
|
});
|
|
139
143
|
```
|
|
140
144
|
|
|
141
|
-
|
|
145
|
+
`kojee-mcp tail` is a portable line-streamer shipped with this proxy —
|
|
146
|
+
works the same on macOS, Linux, and Windows. The proxy interpolates the
|
|
147
|
+
resolved `eventLogPath` into the Channel-`instructions` string so the
|
|
148
|
+
agent receives a ready-to-run command.
|
|
149
|
+
|
|
150
|
+
The `<discoveryKey>` embedded in the log filename is computed from `sha256(CLAUDE_PROJECT_DIR).slice(0,12) + '-' + <ccPid>` where `ccPid` is the parent Claude Code process. (This replaces the pre-v0.4 scheme that used `CLAUDE_CODE_SESSION_ID`, which Claude.app doesn't forward to MCP servers.) The matching `~/.kojee/sessions/cc-<discoveryKey>.json` discovery file lets the Stop / UserPromptSubmit hooks find this proxy via the same independent derivation.
|
|
142
151
|
|
|
143
152
|
Each appended event line arrives as a separate spontaneous wake notification. The agent stays free to chat with the user between events; CC delivers each event from idle as it arrives.
|
|
144
153
|
|
|
145
|
-
**Stop hook
|
|
154
|
+
**Stop hook always long-polls** (up to 30s): the queue's `markMonitorDelivered` dedup ensures every event is delivered exactly once across the Channel / Monitor / Stop-hook paths, so the hook doesn't need to probe whether a Monitor is running. If the agent skipped the session-start Monitor spawn, the long-poll backstop still catches events; if Monitor is running, the dedup filters out anything already delivered push-style.
|
|
146
155
|
|
|
147
156
|
Channel notifications (when available — see "Claude Code Channels Support" above) supplement this with mid-turn `<channel>` tag delivery, but Monitor is the default no-allowlist wake path that works for every user.
|
|
148
157
|
|
|
158
|
+
**Cross-platform support:** the proxy core, the `kojee-mcp tail` Monitor command, and the Channel wake path are portable across macOS, Linux, and Windows — path resolution uses `os.homedir()` / `os.tmpdir()` and process-ancestry detection uses the pure-JS `ps-list` package. Claude Code hook invocation on Windows is pending end-to-end verification; on macOS and Linux it is exercised by CI.
|
|
159
|
+
|
|
149
160
|
For one-shot blocking waits (return as soon as a single reply arrives), call `tandem_listen(tandem_id, since=cursor, timeout_ms=N)` instead.
|
|
150
161
|
|
|
151
162
|
## Backend SSE Wire Compatibility
|
|
@@ -1,36 +1,26 @@
|
|
|
1
1
|
// src/runtime/ancestry.ts
|
|
2
|
-
import
|
|
2
|
+
import psList from "ps-list";
|
|
3
3
|
import { createHash } from "crypto";
|
|
4
|
-
function findClaudeAncestorPid(startPid = process.ppid) {
|
|
5
|
-
|
|
4
|
+
async function findClaudeAncestorPid(startPid = process.ppid) {
|
|
5
|
+
let processes;
|
|
6
|
+
try {
|
|
7
|
+
processes = await psList();
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const byPid = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const p of processes) byPid.set(p.pid, p);
|
|
6
13
|
let pid = startPid;
|
|
7
14
|
for (let depth = 0; depth < 20 && pid !== void 0 && pid > 1; depth++) {
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
row = readProcInfo(pid);
|
|
11
|
-
} catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
15
|
+
const row = byPid.get(pid);
|
|
14
16
|
if (!row) return null;
|
|
15
|
-
|
|
17
|
+
const haystack = `${row.name} ${row.cmd ?? ""}`;
|
|
18
|
+
if (/\bclaude\b/i.test(haystack)) return pid;
|
|
16
19
|
if (row.ppid === void 0 || row.ppid === pid) return null;
|
|
17
20
|
pid = row.ppid;
|
|
18
21
|
}
|
|
19
22
|
return null;
|
|
20
23
|
}
|
|
21
|
-
function readProcInfo(pid) {
|
|
22
|
-
const out = childProcess.execFileSync("ps", ["-ww", "-p", String(pid), "-o", "ppid=,command="], {
|
|
23
|
-
encoding: "utf8",
|
|
24
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
25
|
-
}).trim();
|
|
26
|
-
if (!out) return null;
|
|
27
|
-
const firstSpace = out.indexOf(" ");
|
|
28
|
-
if (firstSpace < 0) return null;
|
|
29
|
-
const ppid = Number.parseInt(out.slice(0, firstSpace).trim(), 10);
|
|
30
|
-
if (!Number.isFinite(ppid)) return null;
|
|
31
|
-
const command = out.slice(firstSpace + 1).trim();
|
|
32
|
-
return { command, ppid };
|
|
33
|
-
}
|
|
34
24
|
function deriveDiscoveryKey(projectDir, ccPid) {
|
|
35
25
|
const hasProjectDir = typeof projectDir === "string" && projectDir.length > 0;
|
|
36
26
|
if (!hasProjectDir && ccPid === null) {
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/auth/paired-config.ts
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
function pairedConfigPath() {
|
|
5
|
-
return path.join(
|
|
6
|
+
return path.join(os.homedir(), ".kojee", "config.json");
|
|
6
7
|
}
|
|
7
8
|
function loadPairedConfig(filePath = pairedConfigPath()) {
|
|
8
9
|
try {
|