patchcord 0.3.56 → 0.3.57
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/bin/patchcord.mjs +145 -31
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -480,26 +480,21 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
480
480
|
let token = "";
|
|
481
481
|
let identity = "";
|
|
482
482
|
let serverUrl = "https://mcp.patchcord.dev";
|
|
483
|
+
let apiUrl = "https://api.patchcord.dev";
|
|
484
|
+
let clientType = "";
|
|
483
485
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
while (!identity) {
|
|
488
|
-
token = (await ask(`\n${bold}Paste your agent token:${r} `)).trim();
|
|
489
|
-
|
|
490
|
-
if (!token) {
|
|
491
|
-
console.error("Token is required. Get one from your patchcord dashboard.");
|
|
492
|
-
rl.close();
|
|
493
|
-
process.exit(1);
|
|
494
|
-
}
|
|
486
|
+
// --token bypass for power users / CI
|
|
487
|
+
const tokenFlag = flags.find(f => f.startsWith("--token="))?.split("=")[1]
|
|
488
|
+
|| (flags.includes("--token") ? flags[flags.indexOf("--token") + 1] : "");
|
|
495
489
|
|
|
490
|
+
if (tokenFlag) {
|
|
491
|
+
token = tokenFlag.trim();
|
|
496
492
|
if (!isSafeToken(token)) {
|
|
497
|
-
console.
|
|
493
|
+
console.error("Invalid token format.");
|
|
498
494
|
rl.close();
|
|
499
495
|
process.exit(1);
|
|
500
496
|
}
|
|
501
|
-
|
|
502
|
-
console.log("Validating...");
|
|
497
|
+
console.log("Validating token...");
|
|
503
498
|
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
504
499
|
if (validateResp) {
|
|
505
500
|
try {
|
|
@@ -509,30 +504,149 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
509
504
|
} catch {}
|
|
510
505
|
}
|
|
511
506
|
if (!identity) {
|
|
512
|
-
console.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
rl.close();
|
|
516
|
-
process.exit(1);
|
|
517
|
-
}
|
|
507
|
+
console.error("Token not recognized.");
|
|
508
|
+
rl.close();
|
|
509
|
+
process.exit(1);
|
|
518
510
|
}
|
|
519
|
-
|
|
511
|
+
rl.close();
|
|
512
|
+
} else {
|
|
513
|
+
// Browser connect flow
|
|
514
|
+
rl.close();
|
|
520
515
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
516
|
+
function canOpenBrowser() {
|
|
517
|
+
if (process.env.SSH_CLIENT || process.env.SSH_TTY) return false;
|
|
518
|
+
if (!process.env.DISPLAY && process.platform === "linux") return false;
|
|
519
|
+
if (flags.includes("--no-browser")) return false;
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function openBrowser(url) {
|
|
524
|
+
try {
|
|
525
|
+
if (process.platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
|
|
526
|
+
else if (process.platform === "win32") execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
527
|
+
else execSync(`xdg-open "${url}"`, { stdio: "ignore" });
|
|
528
|
+
return true;
|
|
529
|
+
} catch { return false; }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Create session
|
|
533
|
+
let sessionId = "";
|
|
534
|
+
try {
|
|
535
|
+
const resp = run(`curl -sf --max-time 10 -X POST "${apiUrl}/api/connect/session" -H "Content-Type: application/json" -d '{"tool":"${choice}"}'`);
|
|
536
|
+
if (resp) {
|
|
537
|
+
const data = JSON.parse(resp);
|
|
538
|
+
sessionId = data.session_id || "";
|
|
539
|
+
}
|
|
540
|
+
} catch {}
|
|
541
|
+
|
|
542
|
+
if (!sessionId) {
|
|
543
|
+
// Fallback to manual token paste if connect API unavailable
|
|
544
|
+
console.log(`\n${dim}Browser connect unavailable. Paste token manually.${r}`);
|
|
545
|
+
console.log(`${dim}Get your token at:${r} ${cyan}https://patchcord.dev/console${r}`);
|
|
546
|
+
const { createInterface: createRL2 } = await import("readline");
|
|
547
|
+
const rl2 = createRL2({ input: process.stdin, output: process.stdout });
|
|
548
|
+
const ask2 = (q) => new Promise((resolve) => rl2.question(q, resolve));
|
|
549
|
+
token = (await ask2(`\n${bold}Paste your agent token:${r} `)).trim();
|
|
550
|
+
rl2.close();
|
|
551
|
+
if (!token || !isSafeToken(token)) {
|
|
552
|
+
console.error("Invalid token.");
|
|
528
553
|
process.exit(1);
|
|
529
554
|
}
|
|
530
|
-
|
|
555
|
+
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
556
|
+
if (validateResp) {
|
|
557
|
+
try {
|
|
558
|
+
const data = JSON.parse(validateResp);
|
|
559
|
+
identity = `${data.agent_id}@${data.namespace_id}`;
|
|
560
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
561
|
+
} catch {}
|
|
562
|
+
}
|
|
563
|
+
if (!identity) {
|
|
564
|
+
console.error("Token not recognized.");
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
} else {
|
|
568
|
+
// Open browser or show URL
|
|
569
|
+
const connectUrl = `https://patchcord.dev/connect?session=${sessionId}`;
|
|
570
|
+
|
|
571
|
+
if (canOpenBrowser()) {
|
|
572
|
+
const opened = openBrowser(connectUrl);
|
|
573
|
+
if (opened) {
|
|
574
|
+
console.log(`\n ${green}✓${r} Browser opened.`);
|
|
575
|
+
} else {
|
|
576
|
+
console.log(`\n ${dim}Could not open browser. Open this URL manually:${r}`);
|
|
577
|
+
console.log(`\n ${cyan}${connectUrl}${r}\n`);
|
|
578
|
+
}
|
|
579
|
+
} else {
|
|
580
|
+
console.log(`\n ${dim}Can't open a browser on this machine.${r}`);
|
|
581
|
+
console.log(` ${dim}Open this URL on any device:${r}`);
|
|
582
|
+
console.log(`\n ${cyan}${connectUrl}${r}\n`);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(` ${dim}⏳ Waiting for you to complete setup in the browser...${r}`);
|
|
586
|
+
console.log(` ${dim} (press Ctrl+C to cancel)${r}\n`);
|
|
587
|
+
|
|
588
|
+
// SSE listener — wait for session completion
|
|
589
|
+
const http = await import("https");
|
|
590
|
+
const sseResult = await new Promise((resolve, reject) => {
|
|
591
|
+
const timeout = setTimeout(() => {
|
|
592
|
+
reject(new Error("Session expired. Run npx patchcord@latest again."));
|
|
593
|
+
}, 5 * 60 * 1000);
|
|
594
|
+
|
|
595
|
+
function connect() {
|
|
596
|
+
const req = http.get(`${apiUrl}/api/connect/session/${sessionId}/wait`, {
|
|
597
|
+
headers: { "Accept": "text/event-stream" },
|
|
598
|
+
}, (res) => {
|
|
599
|
+
if (res.statusCode !== 200) {
|
|
600
|
+
clearTimeout(timeout);
|
|
601
|
+
reject(new Error(`Server returned ${res.statusCode}`));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
let buffer = "";
|
|
605
|
+
res.on("data", (chunk) => {
|
|
606
|
+
buffer += chunk.toString();
|
|
607
|
+
const lines = buffer.split("\n");
|
|
608
|
+
buffer = lines.pop();
|
|
609
|
+
for (const line of lines) {
|
|
610
|
+
if (line.startsWith("data: ")) {
|
|
611
|
+
try {
|
|
612
|
+
const payload = JSON.parse(line.slice(6));
|
|
613
|
+
if (payload.error) {
|
|
614
|
+
clearTimeout(timeout);
|
|
615
|
+
reject(new Error(payload.error));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (payload.token) {
|
|
619
|
+
clearTimeout(timeout);
|
|
620
|
+
resolve(payload);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
} catch {}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
res.on("end", () => {
|
|
628
|
+
// Connection dropped — retry
|
|
629
|
+
setTimeout(connect, 2000);
|
|
630
|
+
});
|
|
631
|
+
res.on("error", () => {
|
|
632
|
+
setTimeout(connect, 2000);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
req.on("error", () => {
|
|
636
|
+
setTimeout(connect, 2000);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
connect();
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
token = sseResult.token;
|
|
644
|
+
identity = `${sseResult.agent_id}@${sseResult.namespace_id}`;
|
|
645
|
+
clientType = sseResult.client_type || "";
|
|
646
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r} connected.`);
|
|
531
647
|
}
|
|
532
648
|
}
|
|
533
649
|
|
|
534
|
-
rl.close();
|
|
535
|
-
|
|
536
650
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
537
651
|
|
|
538
652
|
if (isCursor) {
|