patchcord 0.3.43 → 0.3.46
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 +56 -34
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -20,6 +20,10 @@ function run(cmd) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function isSafeToken(t) {
|
|
24
|
+
return /^[A-Za-z0-9_\-=+/.]+$/.test(t) && t.length < 200;
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
if (cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
24
28
|
console.log(`patchcord — agent messaging for AI coding agents
|
|
25
29
|
|
|
@@ -404,6 +408,22 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
404
408
|
} catch {}
|
|
405
409
|
}
|
|
406
410
|
} else if (isCodex) {
|
|
411
|
+
// Check global config for stale patchcord MCP — user may have run installer in ~ by mistake
|
|
412
|
+
const globalCodexConfig = join(HOME, ".codex", "config.toml");
|
|
413
|
+
if (existsSync(globalCodexConfig)) {
|
|
414
|
+
const globalContent = readFileSync(globalCodexConfig, "utf-8");
|
|
415
|
+
if (globalContent.includes("[mcp_servers.patchcord]")) {
|
|
416
|
+
console.log(`\n ${red}⚠ Patchcord is in your GLOBAL Codex config!${r}`);
|
|
417
|
+
console.log(` ${dim}${globalCodexConfig}${r}`);
|
|
418
|
+
console.log(` ${yellow}This overrides per-project config and causes conflicts.${r}`);
|
|
419
|
+
const cleanGlobal = (await ask(` ${dim}Remove patchcord from global config? (Y/n):${r} `)).trim().toLowerCase();
|
|
420
|
+
if (cleanGlobal !== "n" && cleanGlobal !== "no") {
|
|
421
|
+
const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
422
|
+
writeFileSync(globalCodexConfig, cleaned + "\n");
|
|
423
|
+
console.log(` ${green}✓${r} Removed from global config`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
407
427
|
const configPath = join(cwd, ".codex", "config.toml");
|
|
408
428
|
if (existsSync(configPath)) {
|
|
409
429
|
const content = readFileSync(configPath, "utf-8");
|
|
@@ -436,6 +456,12 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
436
456
|
process.exit(1);
|
|
437
457
|
}
|
|
438
458
|
|
|
459
|
+
if (!isSafeToken(token)) {
|
|
460
|
+
console.log(` ${red}✗${r} Invalid token format`);
|
|
461
|
+
rl.close();
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
439
465
|
console.log("Validating...");
|
|
440
466
|
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
441
467
|
if (validateResp) {
|
|
@@ -459,19 +485,6 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
459
485
|
if (customUrl === "y" || customUrl === "yes") {
|
|
460
486
|
const url = (await ask("Server URL: ")).trim();
|
|
461
487
|
if (url) serverUrl = url;
|
|
462
|
-
|
|
463
|
-
// Re-validate against custom server if identity wasn't found
|
|
464
|
-
if (!identity) {
|
|
465
|
-
console.log("Validating token...");
|
|
466
|
-
const resp2 = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
467
|
-
if (resp2) {
|
|
468
|
-
try {
|
|
469
|
-
const data = JSON.parse(resp2);
|
|
470
|
-
identity = `${data.agent_id}@${data.namespace_id}`;
|
|
471
|
-
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
472
|
-
} catch {}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
488
|
}
|
|
476
489
|
|
|
477
490
|
rl.close();
|
|
@@ -489,7 +502,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
489
502
|
command: "npx",
|
|
490
503
|
args: [
|
|
491
504
|
"-y", "mcp-remote",
|
|
492
|
-
serverUrl
|
|
505
|
+
`${serverUrl}/mcp`,
|
|
493
506
|
"--header",
|
|
494
507
|
`Authorization: Bearer ${token}`,
|
|
495
508
|
"--header",
|
|
@@ -522,7 +535,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
522
535
|
command: "npx",
|
|
523
536
|
args: [
|
|
524
537
|
"-y", "mcp-remote",
|
|
525
|
-
serverUrl
|
|
538
|
+
`${serverUrl}/mcp`,
|
|
526
539
|
"--header",
|
|
527
540
|
`Authorization: Bearer ${token}`,
|
|
528
541
|
"--header",
|
|
@@ -546,14 +559,8 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
546
559
|
mkdirSync(join(HOME, ".codeium", "windsurf"), { recursive: true });
|
|
547
560
|
writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
|
|
548
561
|
}
|
|
549
|
-
// Install workflows as slash commands (.windsurf/workflows/) — per-project
|
|
550
|
-
const wsWorkflowDir = join(cwd, ".windsurf", "workflows");
|
|
551
|
-
mkdirSync(wsWorkflowDir, { recursive: true });
|
|
552
|
-
cpSync(join(pluginRoot, "skills", "inbox", "SKILL.md"), join(wsWorkflowDir, "patchcord.md"));
|
|
553
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(wsWorkflowDir, "patchcord-wait.md"));
|
|
554
562
|
console.log(`\n ${green}✓${r} Windsurf configured: ${dim}${wsPath}${r}`);
|
|
555
|
-
console.log(` ${
|
|
556
|
-
console.log(` ${yellow}MCP config is global — all Windsurf projects share this agent.${r}`);
|
|
563
|
+
console.log(` ${yellow}Global config — all Windsurf projects share this agent.${r}`);
|
|
557
564
|
} else if (isGemini) {
|
|
558
565
|
// Gemini CLI: global only (~/.gemini/settings.json)
|
|
559
566
|
const geminiPath = join(HOME, ".gemini", "settings.json");
|
|
@@ -670,19 +677,18 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
670
677
|
let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
671
678
|
// Remove old patchcord config block if present
|
|
672
679
|
existing = existing.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
673
|
-
|
|
674
|
-
|
|
680
|
+
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nhttp_headers = { "Authorization" = "Bearer ${token}", "X-Patchcord-Machine" = "${hostname}" }\n`;
|
|
681
|
+
writeFileSync(configPath, existing);
|
|
682
|
+
// Clean up any PATCHCORD_TOKEN we previously wrote to .env
|
|
675
683
|
const envPath = join(cwd, ".env");
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
684
|
+
if (existsSync(envPath)) {
|
|
685
|
+
const envContent = readFileSync(envPath, "utf-8");
|
|
686
|
+
if (envContent.includes("PATCHCORD_TOKEN=")) {
|
|
687
|
+
const cleaned = envContent.replace(/^PATCHCORD_TOKEN=.*\n?/gm, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
688
|
+
writeFileSync(envPath, cleaned ? cleaned + "\n" : "");
|
|
689
|
+
console.log(` ${green}✓${r} Cleaned PATCHCORD_TOKEN from .env`);
|
|
690
|
+
}
|
|
682
691
|
}
|
|
683
|
-
writeFileSync(envPath, envContent);
|
|
684
|
-
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nbearer_token_env_var = "${envName}"\n`;
|
|
685
|
-
writeFileSync(configPath, existing);
|
|
686
692
|
// Slash commands (.codex/prompts/)
|
|
687
693
|
const codexPromptsDir = join(codexDir, "prompts");
|
|
688
694
|
mkdirSync(codexPromptsDir, { recursive: true });
|
|
@@ -721,6 +727,22 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
721
727
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
722
728
|
}
|
|
723
729
|
|
|
730
|
+
// Warn about gitignore for per-project configs with tokens
|
|
731
|
+
if (!isWindsurf && !isGemini && !isZed) {
|
|
732
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
733
|
+
const configFile = isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : ".mcp.json";
|
|
734
|
+
let needsWarning = true;
|
|
735
|
+
if (existsSync(gitignorePath)) {
|
|
736
|
+
const gi = readFileSync(gitignorePath, "utf-8");
|
|
737
|
+
if (gi.includes(configFile) || gi.includes(".mcp.json") || gi.includes(".codex/") || gi.includes(".cursor/")) {
|
|
738
|
+
needsWarning = false;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (needsWarning) {
|
|
742
|
+
console.log(`\n ${yellow}⚠ Add ${configFile} to .gitignore — it contains your token${r}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
724
746
|
const toolName = isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
725
747
|
console.log(`\n${dim}Restart your ${toolName} session, then run:${r} ${bold}inbox()${r}`);
|
|
726
748
|
process.exit(0);
|
|
@@ -761,7 +783,7 @@ if (cmd === "skill") {
|
|
|
761
783
|
const baseUrl = mcpUrl.replace(/\/mcp(\/bearer)?$/, "");
|
|
762
784
|
const token = auth.replace(/^Bearer\s+/, "");
|
|
763
785
|
|
|
764
|
-
if (!baseUrl || !token) {
|
|
786
|
+
if (!baseUrl || !token || !isSafeToken(token)) {
|
|
765
787
|
console.error("Cannot read patchcord URL/token from .mcp.json");
|
|
766
788
|
process.exit(1);
|
|
767
789
|
}
|