@lifeaitools/clauth 1.5.78 → 1.5.81
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/cli/assets/codevelop/launcher-active.cmd.template +20 -0
- package/cli/assets/codevelop/launcher-static.cmd.template +7 -0
- package/cli/assets/codevelop/windows-terminal.profiles.json +48 -0
- package/cli/commands/codevelop.js +907 -0
- package/cli/commands/serve.js +624 -23
- package/cli/index.js +40 -0
- package/cli/studio-debug.js +0 -37
- package/install.ps1 +21 -7
- package/package.json +61 -61
- package/scripts/postinstall.js +20 -0
package/cli/commands/serve.js
CHANGED
|
@@ -17,10 +17,10 @@ import ora from "ora";
|
|
|
17
17
|
import { execSync as execSyncTop } from "child_process";
|
|
18
18
|
import Conf from "conf";
|
|
19
19
|
import { getConfOptions } from "../conf-path.js";
|
|
20
|
-
import { readdir, readFile, writeFile, rm, mkdir, stat, rename } from "node:fs/promises";
|
|
21
|
-
import fg from "fast-glob";
|
|
22
|
-
import { rgPath } from "@vscode/ripgrep";
|
|
23
|
-
import { createStudioDebugRuntime } from "../studio-debug.js";
|
|
20
|
+
import { readdir, readFile, writeFile, rm, mkdir, stat, rename } from "node:fs/promises";
|
|
21
|
+
import fg from "fast-glob";
|
|
22
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
23
|
+
import { createStudioDebugRuntime } from "../studio-debug.js";
|
|
24
24
|
|
|
25
25
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
26
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
|
|
@@ -564,6 +564,9 @@ function dashboardHtml(port, whitelist, isStaged = false) {
|
|
|
564
564
|
.tunnel-url{font-family:'Courier New',monospace;font-size:.78rem;color:#60a5fa;word-break:break-all}
|
|
565
565
|
.tunnel-url a{color:#60a5fa;text-decoration:none}
|
|
566
566
|
.tunnel-url a:hover{text-decoration:underline}
|
|
567
|
+
.btn-ccandme{background:linear-gradient(135deg,#1a1a2e,#16213e);color:#a78bfa;border:1px solid #4c1d95;padding:7px 16px;font-size:.85rem;border-radius:7px;cursor:pointer;font-weight:600;transition:all .15s;white-space:nowrap}
|
|
568
|
+
.btn-ccandme:hover{background:linear-gradient(135deg,#2d1b69,#1e1b4b);border-color:#7c3aed;color:#c4b5fd;transform:translateY(-1px)}
|
|
569
|
+
.btn-ccandme:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
|
567
570
|
.btn-claude{background:linear-gradient(135deg,#d97706,#f59e0b);color:#0a0f1a;padding:8px 18px;font-size:.85rem;border-radius:7px;border:none;cursor:pointer;font-weight:700;letter-spacing:.3px;transition:all .15s;white-space:nowrap}
|
|
568
571
|
.btn-claude:hover{filter:brightness(1.1);transform:translateY(-1px)}
|
|
569
572
|
.btn-claude:disabled{opacity:.4;cursor:not-allowed;transform:none;filter:none}
|
|
@@ -743,6 +746,7 @@ function dashboardHtml(port, whitelist, isStaged = false) {
|
|
|
743
746
|
<button class="btn-refresh" onclick="loadServices()">↻ Refresh</button>
|
|
744
747
|
<button class="btn-add" onclick="toggleAddService()">+ Add Service</button>
|
|
745
748
|
<button class="btn-check" id="check-btn" onclick="checkAll()">⬤ Check All</button>
|
|
749
|
+
<button class="btn-ccandme" id="ccandme-btn" onclick="launchCCandMe()" title="Launch CCandMe — 4-pane WezTerm: Claude + Codex + Me">⚡ CCandMe</button>
|
|
746
750
|
<button class="btn-lock" onclick="lockVault()">🔒 Lock</button>
|
|
747
751
|
<button class="btn-stop" onclick="restartDaemon()" style="background:#1a2e1a;border:1px solid #166534;color:#86efac" title="Restart daemon — keeps vault unlocked">↺ Restart</button>
|
|
748
752
|
<button class="btn-stop" onclick="stopDaemon()" style="background:#7f1d1d;border:1px solid #991b1b;color:#fca5a5" title="Stop daemon — password required on next start">⏹ Stop</button>
|
|
@@ -1115,6 +1119,24 @@ async function makeLive() {
|
|
|
1115
1119
|
}
|
|
1116
1120
|
}
|
|
1117
1121
|
|
|
1122
|
+
// ── Launch CCandMe (4-pane WezTerm: Claude + Codex + Me) ──
|
|
1123
|
+
async function launchCCandMe() {
|
|
1124
|
+
const btn = document.getElementById("ccandme-btn");
|
|
1125
|
+
if (btn) { btn.disabled = true; btn.textContent = "⚡ Launching…"; }
|
|
1126
|
+
try {
|
|
1127
|
+
const r = await fetch(BASE + "/launch-ccandme", { method: "POST" }).then(r => r.json()).catch(() => ({}));
|
|
1128
|
+
if (r.ok) {
|
|
1129
|
+
if (btn) { btn.textContent = "⚡ Launched!"; setTimeout(() => { btn.disabled = false; btn.textContent = "⚡ CCandMe"; }, 3000); }
|
|
1130
|
+
} else {
|
|
1131
|
+
alert("CCandMe launch failed: " + (r.error || "unknown error"));
|
|
1132
|
+
if (btn) { btn.disabled = false; btn.textContent = "⚡ CCandMe"; }
|
|
1133
|
+
}
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
alert("CCandMe launch failed: " + err.message);
|
|
1136
|
+
if (btn) { btn.disabled = false; btn.textContent = "⚡ CCandMe"; }
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1118
1140
|
// ── Restart daemon (keeps boot.key — vault stays unlocked) ──
|
|
1119
1141
|
async function restartDaemon() {
|
|
1120
1142
|
if (!confirm("Restart the daemon?\\n\\nThe vault will stay unlocked (boot.key kept).")) return;
|
|
@@ -2406,7 +2428,9 @@ function readBody(req) {
|
|
|
2406
2428
|
}
|
|
2407
2429
|
|
|
2408
2430
|
// ── Server logic (shared by foreground + daemon) ─────────────
|
|
2409
|
-
function createServer(initPassword, whitelist, port, tunnelHostnameInit = null, isStaged = false) {
|
|
2431
|
+
function createServer(initPassword, whitelist, port, tunnelHostnameInit = null, isStaged = false) {
|
|
2432
|
+
mcpHttpBaseUrl = `http://127.0.0.1:${port}`;
|
|
2433
|
+
|
|
2410
2434
|
// tunnelHostname may be updated at runtime (fetched from DB after unlock)
|
|
2411
2435
|
let tunnelHostname = tunnelHostnameInit;
|
|
2412
2436
|
|
|
@@ -2436,12 +2460,12 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
2436
2460
|
// Rotation engine — starts after unlock
|
|
2437
2461
|
const rotationEngine = createRotationEngine(initPassword, machineHash, LOG_FILE);
|
|
2438
2462
|
|
|
2439
|
-
const CORS = {
|
|
2463
|
+
const CORS = {
|
|
2440
2464
|
"Access-Control-Allow-Origin": "*",
|
|
2441
2465
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
2442
2466
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, Mcp-Session-Id, mcp-protocol-version, mcp-session-id",
|
|
2443
|
-
};
|
|
2444
|
-
const studioDebugRuntime = createStudioDebugRuntime({ port, logFile: LOG_FILE });
|
|
2467
|
+
};
|
|
2468
|
+
const studioDebugRuntime = createStudioDebugRuntime({ port, logFile: LOG_FILE });
|
|
2445
2469
|
|
|
2446
2470
|
// ── MCP SSE session tracking ──────────────────────────────
|
|
2447
2471
|
const sseSessions = new Map(); // sessionId → { res, initialized }
|
|
@@ -2842,14 +2866,14 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
2842
2866
|
return strike(res, 403, `Rejected non-local address: ${remote}`);
|
|
2843
2867
|
}
|
|
2844
2868
|
|
|
2845
|
-
// CORS preflight
|
|
2846
|
-
if (req.method === "OPTIONS") {
|
|
2847
|
-
res.writeHead(204, CORS);
|
|
2848
|
-
return res.end();
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
const studioDebugHandled = await studioDebugRuntime.handle(req, res, url, CORS);
|
|
2852
|
-
if (studioDebugHandled !== false) return;
|
|
2869
|
+
// CORS preflight
|
|
2870
|
+
if (req.method === "OPTIONS") {
|
|
2871
|
+
res.writeHead(204, CORS);
|
|
2872
|
+
return res.end();
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
const studioDebugHandled = await studioDebugRuntime.handle(req, res, url, CORS);
|
|
2876
|
+
if (studioDebugHandled !== false) return;
|
|
2853
2877
|
|
|
2854
2878
|
// ── Hosts that bypass OAuth (fresh domains for claude.ai compatibility) ──
|
|
2855
2879
|
const NOAUTH_HOSTS = ["fs.regendevcorp.com", "clauth.regendevcorp.com", "chitchat.regendevcorp.com"];
|
|
@@ -3047,13 +3071,14 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3047
3071
|
}
|
|
3048
3072
|
|
|
3049
3073
|
// ── MCP path helpers ──
|
|
3050
|
-
const MCP_PATHS = ["/mcp", "/gws", "/clauth", "/fs", "/chitchat"];
|
|
3074
|
+
const MCP_PATHS = ["/mcp", "/gws", "/clauth", "/fs", "/chitchat", "/codevelop"];
|
|
3051
3075
|
const isMcpPath = MCP_PATHS.includes(reqPath);
|
|
3052
3076
|
function toolsForPath(p) {
|
|
3053
3077
|
if (p === "/gws") return MCP_TOOLS.filter(t => t.name.startsWith("gws_"));
|
|
3054
3078
|
if (p === "/clauth") return MCP_TOOLS.filter(t => t.name.startsWith("clauth_") || t.name === "monkey_dispatch" || t.name.startsWith("terminal_") || t.name.startsWith("channel_"));
|
|
3055
3079
|
if (p === "/fs") return MCP_TOOLS.filter(t => t.name.startsWith("fs_"));
|
|
3056
3080
|
if (p === "/chitchat") return MCP_TOOLS.filter(t => t.name.startsWith("chitchat_"));
|
|
3081
|
+
if (p === "/codevelop") return MCP_TOOLS.filter(t => t.name.startsWith("codevelop_"));
|
|
3057
3082
|
return MCP_TOOLS; // /mcp — all tools
|
|
3058
3083
|
}
|
|
3059
3084
|
function serverNameForPath(p) {
|
|
@@ -3061,6 +3086,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3061
3086
|
if (p === "/clauth") return "clauth";
|
|
3062
3087
|
if (p === "/fs") return "fs";
|
|
3063
3088
|
if (p === "/chitchat") return "chitchat";
|
|
3089
|
+
if (p === "/codevelop") return "codevelop";
|
|
3064
3090
|
return "clauth";
|
|
3065
3091
|
}
|
|
3066
3092
|
|
|
@@ -3325,6 +3351,122 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3325
3351
|
});
|
|
3326
3352
|
}
|
|
3327
3353
|
|
|
3354
|
+
// ── Co-development transport — peer-aware Claude/Codex sessions ─────
|
|
3355
|
+
// This is intentionally separate from /chitchat so existing relay behavior
|
|
3356
|
+
// cannot leak into terminal peer routing.
|
|
3357
|
+
if (method === "POST" && reqPath === "/codevelop/start") {
|
|
3358
|
+
let body;
|
|
3359
|
+
try { body = await readBody(req); } catch {
|
|
3360
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3361
|
+
return res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3362
|
+
}
|
|
3363
|
+
const result = startCodevelopSession(body || {});
|
|
3364
|
+
return ok(res, result);
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
if (method === "POST" && reqPath === "/codevelop/join") {
|
|
3368
|
+
let body;
|
|
3369
|
+
try { body = await readBody(req); } catch {
|
|
3370
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3371
|
+
return res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3372
|
+
}
|
|
3373
|
+
const result = joinCodevelopSession(body || {});
|
|
3374
|
+
if (result.error) {
|
|
3375
|
+
res.writeHead(result.error === "not_found" ? 404 : 400, { "Content-Type": "application/json", ...CORS });
|
|
3376
|
+
return res.end(JSON.stringify(result));
|
|
3377
|
+
}
|
|
3378
|
+
return ok(res, result);
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
if (method === "POST" && reqPath === "/codevelop/send") {
|
|
3382
|
+
let body;
|
|
3383
|
+
try { body = await readBody(req); } catch {
|
|
3384
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3385
|
+
return res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3386
|
+
}
|
|
3387
|
+
const result = sendCodevelopMessage(body || {});
|
|
3388
|
+
if (result.error) {
|
|
3389
|
+
res.writeHead(result.error === "not_found" ? 404 : 400, { "Content-Type": "application/json", ...CORS });
|
|
3390
|
+
return res.end(JSON.stringify(result));
|
|
3391
|
+
}
|
|
3392
|
+
return ok(res, result);
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
if (method === "POST" && reqPath === "/codevelop/poll") {
|
|
3396
|
+
let body;
|
|
3397
|
+
try { body = await readBody(req); } catch {
|
|
3398
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3399
|
+
return res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3400
|
+
}
|
|
3401
|
+
const result = pollCodevelopMessages(body || {});
|
|
3402
|
+
if (result.error) {
|
|
3403
|
+
res.writeHead(result.error === "not_found" ? 404 : 400, { "Content-Type": "application/json", ...CORS });
|
|
3404
|
+
return res.end(JSON.stringify(result));
|
|
3405
|
+
}
|
|
3406
|
+
return ok(res, result);
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
if (method === "GET" && reqPath === "/codevelop") {
|
|
3410
|
+
return ok(res, listCodevelopSessions());
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
const codevelopStatusMatch = reqPath.match(/^\/codevelop\/([^/]+)\/status$/);
|
|
3414
|
+
if (method === "GET" && codevelopStatusMatch) {
|
|
3415
|
+
const result = statusCodevelopSession(codevelopStatusMatch[1]);
|
|
3416
|
+
if (result.error) {
|
|
3417
|
+
res.writeHead(404, { "Content-Type": "application/json", ...CORS });
|
|
3418
|
+
return res.end(JSON.stringify(result));
|
|
3419
|
+
}
|
|
3420
|
+
return ok(res, result);
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
const codevelopStreamMatch = reqPath.match(/^\/codevelop\/([^/]+)\/([^/]+)\/stream$/);
|
|
3424
|
+
if (method === "GET" && codevelopStreamMatch) {
|
|
3425
|
+
const session_id = decodeURIComponent(codevelopStreamMatch[1]);
|
|
3426
|
+
const peer_id = decodeURIComponent(codevelopStreamMatch[2]);
|
|
3427
|
+
const result = attachCodevelopStream(session_id, peer_id, res);
|
|
3428
|
+
if (result.error) {
|
|
3429
|
+
res.writeHead(result.error === "not_found" ? 404 : 400, { "Content-Type": "application/json", ...CORS });
|
|
3430
|
+
return res.end(JSON.stringify(result));
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
res.writeHead(200, {
|
|
3434
|
+
"Content-Type": "text/event-stream",
|
|
3435
|
+
"Cache-Control": "no-cache",
|
|
3436
|
+
"Connection": "keep-alive",
|
|
3437
|
+
...CORS,
|
|
3438
|
+
});
|
|
3439
|
+
|
|
3440
|
+
for (const entry of result.queued) {
|
|
3441
|
+
res.write(`event: message\ndata: ${JSON.stringify(entry)}\n\n`);
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
const keepalive = setInterval(() => {
|
|
3445
|
+
try { res.write(": keepalive\n\n"); } catch {}
|
|
3446
|
+
}, 15_000);
|
|
3447
|
+
|
|
3448
|
+
req.on("close", () => {
|
|
3449
|
+
clearInterval(keepalive);
|
|
3450
|
+
detachCodevelopStream(session_id, peer_id, res);
|
|
3451
|
+
});
|
|
3452
|
+
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
if (method === "POST" && reqPath === "/codevelop/stop") {
|
|
3457
|
+
let body;
|
|
3458
|
+
try { body = await readBody(req); } catch {
|
|
3459
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
3460
|
+
return res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
3461
|
+
}
|
|
3462
|
+
const result = stopCodevelopSession(body?.session_id);
|
|
3463
|
+
if (result.error) {
|
|
3464
|
+
res.writeHead(404, { "Content-Type": "application/json", ...CORS });
|
|
3465
|
+
return res.end(JSON.stringify(result));
|
|
3466
|
+
}
|
|
3467
|
+
return ok(res, result);
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3328
3470
|
// GET /chitchat/<session_id>/stream — SSE push stream for Claude Code (inbox events)
|
|
3329
3471
|
// Claude Code connects once; daemon pushes an event on every chitchat_send call
|
|
3330
3472
|
const inboxStreamMatch = reqPath.match(/^\/chitchat\/([^/]+)\/stream$/);
|
|
@@ -3545,6 +3687,52 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3545
3687
|
return ok(res, { status: tunnelStatus });
|
|
3546
3688
|
}
|
|
3547
3689
|
|
|
3690
|
+
// POST /launch-ccandme — open a new CCandMe WezTerm window without killing existing sessions
|
|
3691
|
+
if (method === "POST" && reqPath === "/launch-ccandme") {
|
|
3692
|
+
if (lockedGuard(res)) return;
|
|
3693
|
+
try {
|
|
3694
|
+
const { spawn } = await import("child_process");
|
|
3695
|
+
const { existsSync, readFileSync, writeFileSync } = await import("fs");
|
|
3696
|
+
|
|
3697
|
+
// Locate WezTerm
|
|
3698
|
+
const wezCandidates = [
|
|
3699
|
+
"C:/Program Files/WezTerm/wezterm.exe",
|
|
3700
|
+
"C:/Program Files (x86)/WezTerm/wezterm.exe",
|
|
3701
|
+
process.env.WEZTERM_EXE,
|
|
3702
|
+
].filter(Boolean);
|
|
3703
|
+
const wezExe = wezCandidates.find(existsSync) || "wezterm";
|
|
3704
|
+
|
|
3705
|
+
// Render the lua config from the CCandMe template (skip the kill-existing step)
|
|
3706
|
+
const CCANDME_DIR = "C:/Dev/CCandMe";
|
|
3707
|
+
const WORK_DIR = "C:/Dev/regen-root";
|
|
3708
|
+
const templatePath = path.join(CCANDME_DIR, "templates", "wezterm.lua");
|
|
3709
|
+
const luaOutPath = path.join(CCANDME_DIR, ".ccandme-wezterm.lua");
|
|
3710
|
+
|
|
3711
|
+
if (existsSync(templatePath)) {
|
|
3712
|
+
const lua = readFileSync(templatePath, "utf8")
|
|
3713
|
+
.replaceAll("__SUPERVISOR_DIR__", CCANDME_DIR.replace(/\//g, "\\\\"))
|
|
3714
|
+
.replaceAll("__WORK_DIR__", WORK_DIR.replace(/\//g, "\\\\"))
|
|
3715
|
+
.replaceAll("__WORKSPACE__", "ccandme")
|
|
3716
|
+
.replaceAll("__CLAUDE_CMD__", "claude")
|
|
3717
|
+
.replaceAll("__CODEX_CMD__", "codex")
|
|
3718
|
+
.replaceAll("__PACKAGE_ROOT__", CCANDME_DIR.replace(/\//g, "\\\\"));
|
|
3719
|
+
writeFileSync(luaOutPath, lua, "utf8");
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
// Launch WezTerm with the CCandMe config — no kill of existing sessions
|
|
3723
|
+
const luaArg = existsSync(luaOutPath) ? luaOutPath : path.join(CCANDME_DIR, ".ccandme-wezterm.lua");
|
|
3724
|
+
const child = spawn(wezExe, ["--config-file", luaArg, "start"], {
|
|
3725
|
+
detached: true,
|
|
3726
|
+
stdio: "ignore",
|
|
3727
|
+
});
|
|
3728
|
+
child.unref();
|
|
3729
|
+
return ok(res, { ok: true, message: "CCandMe launched" });
|
|
3730
|
+
} catch (err) {
|
|
3731
|
+
res.writeHead(500, { "Content-Type": "application/json", ...CORS });
|
|
3732
|
+
return res.end(JSON.stringify({ error: err.message }));
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3548
3736
|
// POST /restart — spawn fresh process then exit (keeps boot.key, vault stays unlocked)
|
|
3549
3737
|
if (method === "POST" && reqPath === "/restart") {
|
|
3550
3738
|
ok(res, { ok: true, message: "restarting" });
|
|
@@ -4970,6 +5158,10 @@ async function verifyAuth(password) {
|
|
|
4970
5158
|
}
|
|
4971
5159
|
|
|
4972
5160
|
async function actionStart(opts) {
|
|
5161
|
+
if (opts.isolated) {
|
|
5162
|
+
return actionForeground(opts);
|
|
5163
|
+
}
|
|
5164
|
+
|
|
4973
5165
|
const isStaged = !!opts.staged || process.env.__CLAUTH_STAGED === "1";
|
|
4974
5166
|
const port = isStaged ? STAGED_PORT : parseInt(opts.port || String(LIVE_PORT), 10);
|
|
4975
5167
|
let password = opts.pw || null;
|
|
@@ -5220,12 +5412,18 @@ async function actionRestart(opts) {
|
|
|
5220
5412
|
|
|
5221
5413
|
async function actionForeground(opts) {
|
|
5222
5414
|
const port = parseInt(opts.port || "52437", 10);
|
|
5223
|
-
const
|
|
5415
|
+
const isolated = !!opts.isolated;
|
|
5416
|
+
const password = isolated ? null : (opts.pw || null);
|
|
5224
5417
|
const tunnelHostname = opts.tunnel || null;
|
|
5225
5418
|
const whitelist = opts.services
|
|
5226
5419
|
? opts.services.split(",").map(s => s.trim().toLowerCase())
|
|
5227
5420
|
: null;
|
|
5228
5421
|
|
|
5422
|
+
if (isolated && (port === LIVE_PORT || port === STAGED_PORT)) {
|
|
5423
|
+
console.log(chalk.red(`\n Refusing isolated mode on reserved port ${port}. Use a separate port such as 53137.\n`));
|
|
5424
|
+
process.exit(1);
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5229
5427
|
if (password) {
|
|
5230
5428
|
console.log(chalk.gray("\n Verifying vault credentials..."));
|
|
5231
5429
|
try {
|
|
@@ -5235,6 +5433,8 @@ async function actionForeground(opts) {
|
|
|
5235
5433
|
process.exit(1);
|
|
5236
5434
|
}
|
|
5237
5435
|
console.log(chalk.green(" ✓ Vault auth verified"));
|
|
5436
|
+
} else if (isolated) {
|
|
5437
|
+
console.log(chalk.yellow("\n Starting isolated passwordless server — credential routes remain locked"));
|
|
5238
5438
|
} else {
|
|
5239
5439
|
console.log(chalk.yellow("\n Starting in locked state — open browser to unlock"));
|
|
5240
5440
|
}
|
|
@@ -5245,7 +5445,7 @@ async function actionForeground(opts) {
|
|
|
5245
5445
|
|
|
5246
5446
|
const server = createServer(password, whitelist, port, tunnelHostname);
|
|
5247
5447
|
server.listen(port, "127.0.0.1", () => {
|
|
5248
|
-
writePid(process.pid, port);
|
|
5448
|
+
if (!isolated) writePid(process.pid, port);
|
|
5249
5449
|
console.log(chalk.green(` clauth serve → http://127.0.0.1:${port}`));
|
|
5250
5450
|
if (tunnelHostname) {
|
|
5251
5451
|
console.log(chalk.cyan(` Tunnel: https://${tunnelHostname}/sse`));
|
|
@@ -5256,10 +5456,9 @@ async function actionForeground(opts) {
|
|
|
5256
5456
|
console.log(chalk.white(` Client Secret: ${server.__oauthClientSecret}`));
|
|
5257
5457
|
console.log(chalk.gray(" (paste these into Advanced Settings when adding the connector)"));
|
|
5258
5458
|
}
|
|
5259
|
-
if (!password) console.log(chalk.cyan(` 👉 Open http://127.0.0.1:${port} to unlock`));
|
|
5459
|
+
if (!password && !isolated) console.log(chalk.cyan(` 👉 Open http://127.0.0.1:${port} to unlock`));
|
|
5260
5460
|
console.log(chalk.gray(" Ctrl+C to stop\n"));
|
|
5261
|
-
|
|
5262
|
-
openBrowser(`http://127.0.0.1:${port}`);
|
|
5461
|
+
if (!isolated) openBrowser(`http://127.0.0.1:${port}`);
|
|
5263
5462
|
});
|
|
5264
5463
|
|
|
5265
5464
|
server.on("error", err => {
|
|
@@ -5273,7 +5472,7 @@ async function actionForeground(opts) {
|
|
|
5273
5472
|
|
|
5274
5473
|
process.on("SIGINT", () => {
|
|
5275
5474
|
console.log(chalk.yellow("\n Stopping clauth serve...\n"));
|
|
5276
|
-
removePid();
|
|
5475
|
+
if (!isolated) removePid();
|
|
5277
5476
|
server.close(() => process.exit(0));
|
|
5278
5477
|
});
|
|
5279
5478
|
}
|
|
@@ -5344,6 +5543,7 @@ function spawnClaudeTask(prompt, jobId, cwd) {
|
|
|
5344
5543
|
// /terminal/send spawns a fresh claude -p with [context + message],
|
|
5345
5544
|
// captures stdout, stores result back in session context.
|
|
5346
5545
|
const terminalSessions = new Map(); // session_id → SessionState
|
|
5546
|
+
let mcpHttpBaseUrl = "http://127.0.0.1:52437";
|
|
5347
5547
|
|
|
5348
5548
|
// ── Channel webhook event queue ───────────────────────────────────────────────
|
|
5349
5549
|
const channelEvents = []; // { id, event, resource, status, url, repository, created_at }
|
|
@@ -5643,6 +5843,249 @@ function stopChitchatSession(session_id) {
|
|
|
5643
5843
|
return { stopped: true, session_id };
|
|
5644
5844
|
}
|
|
5645
5845
|
|
|
5846
|
+
// ── Co-development — peer-aware Claude/Codex terminal relay ───────────────
|
|
5847
|
+
//
|
|
5848
|
+
// This route is intentionally not a chitchat migration path. It is a separate
|
|
5849
|
+
// transport with addressed peer inboxes and a stable JSON envelope.
|
|
5850
|
+
|
|
5851
|
+
function normalizePeerId(peer) {
|
|
5852
|
+
return String(peer || "").trim().toLowerCase();
|
|
5853
|
+
}
|
|
5854
|
+
|
|
5855
|
+
function serializeCodevelopSession(session) {
|
|
5856
|
+
const peers = {};
|
|
5857
|
+
for (const [peer_id, peer] of Object.entries(session.peers || {})) {
|
|
5858
|
+
peers[peer_id] = {
|
|
5859
|
+
peer_id,
|
|
5860
|
+
role: peer.role,
|
|
5861
|
+
target_peer: peer.target_peer || null,
|
|
5862
|
+
joined_at: peer.joined_at,
|
|
5863
|
+
last_seen_at: peer.last_seen_at,
|
|
5864
|
+
queued: peer.inbox.length,
|
|
5865
|
+
streams: peer.streams.length,
|
|
5866
|
+
};
|
|
5867
|
+
}
|
|
5868
|
+
return {
|
|
5869
|
+
session_id: session.session_id,
|
|
5870
|
+
name: session.name,
|
|
5871
|
+
repo: session.repo,
|
|
5872
|
+
status: session.status,
|
|
5873
|
+
started_at: session.started_at,
|
|
5874
|
+
stopped_at: session.stopped_at || null,
|
|
5875
|
+
turn: session.turn || 0,
|
|
5876
|
+
peers,
|
|
5877
|
+
};
|
|
5878
|
+
}
|
|
5879
|
+
|
|
5880
|
+
function startCodevelopSession({ name, repo, metadata } = {}) {
|
|
5881
|
+
const session_id = generateSessionId();
|
|
5882
|
+
const session = {
|
|
5883
|
+
session_id,
|
|
5884
|
+
name: name || `codevelop-${session_id.slice(0, 8)}`,
|
|
5885
|
+
repo: repo || null,
|
|
5886
|
+
status: "active",
|
|
5887
|
+
started_at: new Date().toISOString(),
|
|
5888
|
+
is_codevelop: true,
|
|
5889
|
+
turn: 0,
|
|
5890
|
+
peers: {},
|
|
5891
|
+
history: [],
|
|
5892
|
+
metadata: metadata && typeof metadata === "object" ? metadata : {},
|
|
5893
|
+
};
|
|
5894
|
+
terminalSessions.set(session_id, session);
|
|
5895
|
+
console.log(`[codevelop] started session ${session_id} name=${session.name}`);
|
|
5896
|
+
return serializeCodevelopSession(session);
|
|
5897
|
+
}
|
|
5898
|
+
|
|
5899
|
+
function joinCodevelopSession({ session_id, peer_id, role, target_peer, metadata } = {}) {
|
|
5900
|
+
const session = terminalSessions.get(session_id);
|
|
5901
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${session_id} not found` };
|
|
5902
|
+
if (session.status === "stopped") return { error: "stopped", message: "Session is stopped" };
|
|
5903
|
+
|
|
5904
|
+
const normalizedPeer = normalizePeerId(peer_id);
|
|
5905
|
+
if (!normalizedPeer) return { error: "invalid_peer", message: "peer_id is required" };
|
|
5906
|
+
|
|
5907
|
+
const now = new Date().toISOString();
|
|
5908
|
+
const existing = session.peers[normalizedPeer];
|
|
5909
|
+
session.peers[normalizedPeer] = {
|
|
5910
|
+
peer_id: normalizedPeer,
|
|
5911
|
+
role: role || existing?.role || "participant",
|
|
5912
|
+
target_peer: target_peer ? normalizePeerId(target_peer) : existing?.target_peer || null,
|
|
5913
|
+
joined_at: existing?.joined_at || now,
|
|
5914
|
+
last_seen_at: now,
|
|
5915
|
+
inbox: existing?.inbox || [],
|
|
5916
|
+
streams: existing?.streams || [],
|
|
5917
|
+
metadata: metadata && typeof metadata === "object" ? metadata : existing?.metadata || {},
|
|
5918
|
+
};
|
|
5919
|
+
|
|
5920
|
+
console.log(`[codevelop] session ${session_id} peer=${normalizedPeer} joined role=${session.peers[normalizedPeer].role}`);
|
|
5921
|
+
return serializeCodevelopSession(session);
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
function buildCodevelopEnvelope(session, payload) {
|
|
5925
|
+
const from = normalizePeerId(payload.from);
|
|
5926
|
+
const to = normalizePeerId(payload.to);
|
|
5927
|
+
if (!from || !to) return { error: "invalid_route", message: "from and to are required" };
|
|
5928
|
+
|
|
5929
|
+
session.turn = (session.turn || 0) + 1;
|
|
5930
|
+
const turn_id = payload.turn_id || `turn-${String(session.turn).padStart(4, "0")}`;
|
|
5931
|
+
return {
|
|
5932
|
+
session_id: session.session_id,
|
|
5933
|
+
turn_id,
|
|
5934
|
+
from,
|
|
5935
|
+
to,
|
|
5936
|
+
type: payload.type || "message",
|
|
5937
|
+
role: payload.role || null,
|
|
5938
|
+
skill: payload.skill || null,
|
|
5939
|
+
task: payload.task || payload.message || "",
|
|
5940
|
+
message: payload.message || null,
|
|
5941
|
+
context: payload.context && typeof payload.context === "object" ? payload.context : {},
|
|
5942
|
+
expect: payload.expect && typeof payload.expect === "object" ? payload.expect : {},
|
|
5943
|
+
verdict: payload.verdict || null,
|
|
5944
|
+
summary: payload.summary || null,
|
|
5945
|
+
evidence: Array.isArray(payload.evidence) ? payload.evidence : [],
|
|
5946
|
+
files_changed: Array.isArray(payload.files_changed) ? payload.files_changed : [],
|
|
5947
|
+
commits: Array.isArray(payload.commits) ? payload.commits : [],
|
|
5948
|
+
blockers: Array.isArray(payload.blockers) ? payload.blockers : [],
|
|
5949
|
+
next: Array.isArray(payload.next) ? payload.next : (payload.next_action ? [String(payload.next_action)] : []),
|
|
5950
|
+
sent_at: new Date().toISOString(),
|
|
5951
|
+
};
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
function sendCodevelopMessage(payload = {}) {
|
|
5955
|
+
const session = terminalSessions.get(payload.session_id);
|
|
5956
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${payload.session_id} not found` };
|
|
5957
|
+
if (session.status === "stopped") return { error: "stopped", message: "Session is stopped" };
|
|
5958
|
+
|
|
5959
|
+
const envelope = buildCodevelopEnvelope(session, payload);
|
|
5960
|
+
if (envelope.error) return envelope;
|
|
5961
|
+
|
|
5962
|
+
if (!session.peers[envelope.from]) {
|
|
5963
|
+
session.peers[envelope.from] = {
|
|
5964
|
+
peer_id: envelope.from,
|
|
5965
|
+
role: "participant",
|
|
5966
|
+
target_peer: envelope.to,
|
|
5967
|
+
joined_at: envelope.sent_at,
|
|
5968
|
+
last_seen_at: envelope.sent_at,
|
|
5969
|
+
inbox: [],
|
|
5970
|
+
streams: [],
|
|
5971
|
+
metadata: {},
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
if (!session.peers[envelope.to]) {
|
|
5975
|
+
session.peers[envelope.to] = {
|
|
5976
|
+
peer_id: envelope.to,
|
|
5977
|
+
role: "participant",
|
|
5978
|
+
target_peer: envelope.from,
|
|
5979
|
+
joined_at: envelope.sent_at,
|
|
5980
|
+
last_seen_at: envelope.sent_at,
|
|
5981
|
+
inbox: [],
|
|
5982
|
+
streams: [],
|
|
5983
|
+
metadata: {},
|
|
5984
|
+
};
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5987
|
+
const target = session.peers[envelope.to];
|
|
5988
|
+
target.inbox.push(envelope);
|
|
5989
|
+
session.history.push(envelope);
|
|
5990
|
+
if (session.history.length > 200) session.history = session.history.slice(-200);
|
|
5991
|
+
|
|
5992
|
+
const event = `event: message\ndata: ${JSON.stringify(envelope)}\n\n`;
|
|
5993
|
+
target.streams = target.streams.filter(res => {
|
|
5994
|
+
try { res.write(event); return true; } catch { return false; }
|
|
5995
|
+
});
|
|
5996
|
+
|
|
5997
|
+
console.log(`[codevelop] session ${session.session_id} ${envelope.from}->${envelope.to} ${envelope.turn_id} queued`);
|
|
5998
|
+
return { queued: true, session_id: session.session_id, turn_id: envelope.turn_id, to: envelope.to };
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
function pollCodevelopMessages({ session_id, peer_id } = {}) {
|
|
6002
|
+
const session = terminalSessions.get(session_id);
|
|
6003
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${session_id} not found` };
|
|
6004
|
+
|
|
6005
|
+
const normalizedPeer = normalizePeerId(peer_id);
|
|
6006
|
+
if (!normalizedPeer) return { error: "invalid_peer", message: "peer_id is required" };
|
|
6007
|
+
const peer = session.peers[normalizedPeer];
|
|
6008
|
+
if (!peer) return { error: "not_joined", message: `Peer ${normalizedPeer} has not joined session ${session_id}` };
|
|
6009
|
+
|
|
6010
|
+
peer.last_seen_at = new Date().toISOString();
|
|
6011
|
+
const messages = peer.inbox.splice(0);
|
|
6012
|
+
return {
|
|
6013
|
+
session_id,
|
|
6014
|
+
peer_id: normalizedPeer,
|
|
6015
|
+
status: messages.length ? "ready" : "idle",
|
|
6016
|
+
count: messages.length,
|
|
6017
|
+
messages,
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
|
|
6021
|
+
function attachCodevelopStream(session_id, peer_id, res) {
|
|
6022
|
+
const session = terminalSessions.get(session_id);
|
|
6023
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${session_id} not found` };
|
|
6024
|
+
|
|
6025
|
+
const normalizedPeer = normalizePeerId(peer_id);
|
|
6026
|
+
const peer = session.peers[normalizedPeer];
|
|
6027
|
+
if (!peer) return { error: "not_joined", message: `Peer ${normalizedPeer} has not joined session ${session_id}` };
|
|
6028
|
+
|
|
6029
|
+
peer.last_seen_at = new Date().toISOString();
|
|
6030
|
+
peer.streams.push(res);
|
|
6031
|
+
console.log(`[codevelop] session ${session_id} peer=${normalizedPeer} stream connected (${peer.streams.length} total)`);
|
|
6032
|
+
return { queued: peer.inbox.slice() };
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
function detachCodevelopStream(session_id, peer_id, res) {
|
|
6036
|
+
const session = terminalSessions.get(session_id);
|
|
6037
|
+
if (!session || !session.is_codevelop) return;
|
|
6038
|
+
const normalizedPeer = normalizePeerId(peer_id);
|
|
6039
|
+
const peer = session.peers[normalizedPeer];
|
|
6040
|
+
if (!peer) return;
|
|
6041
|
+
peer.streams = peer.streams.filter(r => r !== res);
|
|
6042
|
+
console.log(`[codevelop] session ${session_id} peer=${normalizedPeer} stream disconnected`);
|
|
6043
|
+
}
|
|
6044
|
+
|
|
6045
|
+
function statusCodevelopSession(session_id) {
|
|
6046
|
+
const session = terminalSessions.get(session_id);
|
|
6047
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${session_id} not found` };
|
|
6048
|
+
return serializeCodevelopSession(session);
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
function listCodevelopSessions() {
|
|
6052
|
+
const sessions = [];
|
|
6053
|
+
for (const [, s] of terminalSessions) {
|
|
6054
|
+
if (s.is_codevelop) sessions.push(serializeCodevelopSession(s));
|
|
6055
|
+
}
|
|
6056
|
+
return { sessions, count: sessions.length };
|
|
6057
|
+
}
|
|
6058
|
+
|
|
6059
|
+
function stopCodevelopSession(session_id) {
|
|
6060
|
+
const session = terminalSessions.get(session_id);
|
|
6061
|
+
if (!session || !session.is_codevelop) return { error: "not_found", message: `Codevelop session ${session_id} not found` };
|
|
6062
|
+
session.status = "stopped";
|
|
6063
|
+
session.stopped_at = new Date().toISOString();
|
|
6064
|
+
|
|
6065
|
+
for (const peer of Object.values(session.peers)) {
|
|
6066
|
+
const entry = {
|
|
6067
|
+
session_id,
|
|
6068
|
+
turn_id: `stop-${Date.now()}`,
|
|
6069
|
+
from: "system",
|
|
6070
|
+
to: peer.peer_id,
|
|
6071
|
+
type: "stop",
|
|
6072
|
+
task: "Session stopped.",
|
|
6073
|
+
context: {},
|
|
6074
|
+
expect: {},
|
|
6075
|
+
sent_at: session.stopped_at,
|
|
6076
|
+
};
|
|
6077
|
+
peer.inbox.push(entry);
|
|
6078
|
+
const event = `event: stop\ndata: ${JSON.stringify(entry)}\n\n`;
|
|
6079
|
+
peer.streams = peer.streams.filter(res => {
|
|
6080
|
+
try { res.write(event); return true; } catch { return false; }
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
|
|
6084
|
+
terminalSessions.delete(session_id);
|
|
6085
|
+
console.log(`[codevelop] stopped session ${session_id}`);
|
|
6086
|
+
return { stopped: true, session_id };
|
|
6087
|
+
}
|
|
6088
|
+
|
|
5646
6089
|
const ENV_MAP = {
|
|
5647
6090
|
"github": "GITHUB_TOKEN",
|
|
5648
6091
|
"supabase-anon": "NEXT_PUBLIC_SUPABASE_ANON_KEY",
|
|
@@ -5998,6 +6441,116 @@ const MCP_TOOLS = [
|
|
|
5998
6441
|
},
|
|
5999
6442
|
},
|
|
6000
6443
|
|
|
6444
|
+
// ── Co-development — peer-aware Claude/Codex terminal sessions ─────────
|
|
6445
|
+
{
|
|
6446
|
+
name: "codevelop_start",
|
|
6447
|
+
description: "Start a peer-aware co-development session. Separate from chitchat; routes messages by session_id/from/to.",
|
|
6448
|
+
inputSchema: {
|
|
6449
|
+
type: "object",
|
|
6450
|
+
properties: {
|
|
6451
|
+
name: { type: "string", description: "Human-readable session name" },
|
|
6452
|
+
repo: { type: "string", description: "Repository path or slug for the session" },
|
|
6453
|
+
metadata: { type: "object", description: "Optional session metadata" },
|
|
6454
|
+
},
|
|
6455
|
+
additionalProperties: false,
|
|
6456
|
+
},
|
|
6457
|
+
},
|
|
6458
|
+
{
|
|
6459
|
+
name: "codevelop_join",
|
|
6460
|
+
description: "Join a co-development session as a stable peer such as claude or codex.",
|
|
6461
|
+
inputSchema: {
|
|
6462
|
+
type: "object",
|
|
6463
|
+
properties: {
|
|
6464
|
+
session_id: { type: "string", description: "Session ID from codevelop_start" },
|
|
6465
|
+
peer_id: { type: "string", description: "Stable peer ID, e.g. claude or codex" },
|
|
6466
|
+
role: { type: "string", description: "Session role, e.g. supervisor or implementation_partner" },
|
|
6467
|
+
target_peer: { type: "string", description: "Default partner peer ID" },
|
|
6468
|
+
metadata: { type: "object", description: "Optional peer metadata" },
|
|
6469
|
+
},
|
|
6470
|
+
required: ["session_id", "peer_id"],
|
|
6471
|
+
additionalProperties: false,
|
|
6472
|
+
},
|
|
6473
|
+
},
|
|
6474
|
+
{
|
|
6475
|
+
name: "codevelop_send",
|
|
6476
|
+
description: "Send a structured addressed turn to a peer in a co-development session.",
|
|
6477
|
+
inputSchema: {
|
|
6478
|
+
type: "object",
|
|
6479
|
+
properties: {
|
|
6480
|
+
session_id: { type: "string" },
|
|
6481
|
+
turn_id: { type: "string" },
|
|
6482
|
+
from: { type: "string" },
|
|
6483
|
+
to: { type: "string" },
|
|
6484
|
+
type: { type: "string", description: "Message type, e.g. audit_request, reply, blocker" },
|
|
6485
|
+
role: { type: "string", description: "Temporary requested turn role" },
|
|
6486
|
+
skill: { type: "string", description: "Optional requested skill, e.g. rdc:review" },
|
|
6487
|
+
task: { type: "string", description: "Task or request body" },
|
|
6488
|
+
message: { type: "string", description: "Plain fallback message" },
|
|
6489
|
+
context: { type: "object", description: "Repo/work item/branch/files metadata" },
|
|
6490
|
+
expect: { type: "object", description: "Expected response format and evidence requirements" },
|
|
6491
|
+
verdict: { type: "string", description: "Reply verdict, e.g. pass, fail, blocked" },
|
|
6492
|
+
summary: { type: "string", description: "Concise reply summary" },
|
|
6493
|
+
evidence: { type: "array", items: { type: "string" }, description: "Evidence strings or checked commands/files" },
|
|
6494
|
+
files_changed: { type: "array", items: { type: "string" }, description: "Files changed by the sender" },
|
|
6495
|
+
commits: { type: "array", items: { type: "string" }, description: "Commit hashes or messages, if any" },
|
|
6496
|
+
blockers: { type: "array", items: { type: "string" }, description: "Blocking issues, if any" },
|
|
6497
|
+
next: { type: "array", items: { type: "string" }, description: "Next recommended actions" },
|
|
6498
|
+
next_action: { type: "string", description: "Single next action shorthand" },
|
|
6499
|
+
},
|
|
6500
|
+
required: ["session_id", "from", "to"],
|
|
6501
|
+
additionalProperties: false,
|
|
6502
|
+
},
|
|
6503
|
+
},
|
|
6504
|
+
{
|
|
6505
|
+
name: "codevelop_poll",
|
|
6506
|
+
description: "Poll and drain all pending messages for one peer.",
|
|
6507
|
+
inputSchema: {
|
|
6508
|
+
type: "object",
|
|
6509
|
+
properties: {
|
|
6510
|
+
session_id: { type: "string" },
|
|
6511
|
+
peer_id: { type: "string" },
|
|
6512
|
+
},
|
|
6513
|
+
required: ["session_id", "peer_id"],
|
|
6514
|
+
additionalProperties: false,
|
|
6515
|
+
},
|
|
6516
|
+
},
|
|
6517
|
+
{
|
|
6518
|
+
name: "codevelop_stream",
|
|
6519
|
+
description: "Return the local SSE stream URL for a peer. Use HTTP GET on that URL to receive events.",
|
|
6520
|
+
inputSchema: {
|
|
6521
|
+
type: "object",
|
|
6522
|
+
properties: {
|
|
6523
|
+
session_id: { type: "string" },
|
|
6524
|
+
peer_id: { type: "string" },
|
|
6525
|
+
},
|
|
6526
|
+
required: ["session_id", "peer_id"],
|
|
6527
|
+
additionalProperties: false,
|
|
6528
|
+
},
|
|
6529
|
+
},
|
|
6530
|
+
{
|
|
6531
|
+
name: "codevelop_status",
|
|
6532
|
+
description: "Return status, peers, queue counts, and stream counts for a co-development session.",
|
|
6533
|
+
inputSchema: {
|
|
6534
|
+
type: "object",
|
|
6535
|
+
properties: {
|
|
6536
|
+
session_id: { type: "string", description: "Optional session ID. Omit to list all codevelop sessions." },
|
|
6537
|
+
},
|
|
6538
|
+
additionalProperties: false,
|
|
6539
|
+
},
|
|
6540
|
+
},
|
|
6541
|
+
{
|
|
6542
|
+
name: "codevelop_stop",
|
|
6543
|
+
description: "Stop a co-development session and notify joined peers.",
|
|
6544
|
+
inputSchema: {
|
|
6545
|
+
type: "object",
|
|
6546
|
+
properties: {
|
|
6547
|
+
session_id: { type: "string" },
|
|
6548
|
+
},
|
|
6549
|
+
required: ["session_id"],
|
|
6550
|
+
additionalProperties: false,
|
|
6551
|
+
},
|
|
6552
|
+
},
|
|
6553
|
+
|
|
6001
6554
|
// ── Google Workspace (gws CLI) ──────────────────────────────────────────
|
|
6002
6555
|
{
|
|
6003
6556
|
name: "gws_run",
|
|
@@ -6838,6 +7391,54 @@ async function handleMcpTool(vault, name, args) {
|
|
|
6838
7391
|
return mcpResult(JSON.stringify(result));
|
|
6839
7392
|
}
|
|
6840
7393
|
|
|
7394
|
+
case "codevelop_start": {
|
|
7395
|
+
return mcpResult(JSON.stringify(startCodevelopSession(args || {})));
|
|
7396
|
+
}
|
|
7397
|
+
|
|
7398
|
+
case "codevelop_join": {
|
|
7399
|
+
const result = joinCodevelopSession(args || {});
|
|
7400
|
+
if (result.error) return mcpError(`${result.error}: ${result.message}`);
|
|
7401
|
+
return mcpResult(JSON.stringify(result));
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
case "codevelop_send": {
|
|
7405
|
+
const result = sendCodevelopMessage(args || {});
|
|
7406
|
+
if (result.error) return mcpError(`${result.error}: ${result.message}`);
|
|
7407
|
+
return mcpResult(JSON.stringify(result));
|
|
7408
|
+
}
|
|
7409
|
+
|
|
7410
|
+
case "codevelop_poll": {
|
|
7411
|
+
const result = pollCodevelopMessages(args || {});
|
|
7412
|
+
if (result.error) return mcpError(`${result.error}: ${result.message}`);
|
|
7413
|
+
return mcpResult(JSON.stringify(result));
|
|
7414
|
+
}
|
|
7415
|
+
|
|
7416
|
+
case "codevelop_stream": {
|
|
7417
|
+
const { session_id, peer_id } = args || {};
|
|
7418
|
+
if (!session_id || !peer_id) return mcpError("session_id and peer_id required");
|
|
7419
|
+
const session = terminalSessions.get(session_id);
|
|
7420
|
+
if (!session || !session.is_codevelop) return mcpError(`not_found: Codevelop session ${session_id} not found`);
|
|
7421
|
+
const peer = session.peers[normalizePeerId(peer_id)];
|
|
7422
|
+
if (!peer) return mcpError(`not_joined: Peer ${peer_id} has not joined session ${session_id}`);
|
|
7423
|
+
const stream_url = `${mcpHttpBaseUrl}/codevelop/${encodeURIComponent(session_id)}/${encodeURIComponent(normalizePeerId(peer_id))}/stream`;
|
|
7424
|
+
return mcpResult(JSON.stringify({ session_id, peer_id: normalizePeerId(peer_id), stream_url }));
|
|
7425
|
+
}
|
|
7426
|
+
|
|
7427
|
+
case "codevelop_status": {
|
|
7428
|
+
const { session_id } = args || {};
|
|
7429
|
+
const result = session_id ? statusCodevelopSession(session_id) : listCodevelopSessions();
|
|
7430
|
+
if (result.error) return mcpError(`${result.error}: ${result.message}`);
|
|
7431
|
+
return mcpResult(JSON.stringify(result));
|
|
7432
|
+
}
|
|
7433
|
+
|
|
7434
|
+
case "codevelop_stop": {
|
|
7435
|
+
const { session_id } = args || {};
|
|
7436
|
+
if (!session_id) return mcpError("session_id required");
|
|
7437
|
+
const result = stopCodevelopSession(session_id);
|
|
7438
|
+
if (result.error) return mcpError(`${result.error}: ${result.message}`);
|
|
7439
|
+
return mcpResult(JSON.stringify(result));
|
|
7440
|
+
}
|
|
7441
|
+
|
|
6841
7442
|
default:
|
|
6842
7443
|
return mcpError(`Unknown tool: ${name}`);
|
|
6843
7444
|
}
|