patchcord 0.3.44 → 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 +40 -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
|
|
|
@@ -452,6 +456,12 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
452
456
|
process.exit(1);
|
|
453
457
|
}
|
|
454
458
|
|
|
459
|
+
if (!isSafeToken(token)) {
|
|
460
|
+
console.log(` ${red}✗${r} Invalid token format`);
|
|
461
|
+
rl.close();
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
455
465
|
console.log("Validating...");
|
|
456
466
|
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
457
467
|
if (validateResp) {
|
|
@@ -475,19 +485,6 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
475
485
|
if (customUrl === "y" || customUrl === "yes") {
|
|
476
486
|
const url = (await ask("Server URL: ")).trim();
|
|
477
487
|
if (url) serverUrl = url;
|
|
478
|
-
|
|
479
|
-
// Re-validate against custom server if identity wasn't found
|
|
480
|
-
if (!identity) {
|
|
481
|
-
console.log("Validating token...");
|
|
482
|
-
const resp2 = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${token}" "${serverUrl}/api/inbox?limit=0"`);
|
|
483
|
-
if (resp2) {
|
|
484
|
-
try {
|
|
485
|
-
const data = JSON.parse(resp2);
|
|
486
|
-
identity = `${data.agent_id}@${data.namespace_id}`;
|
|
487
|
-
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
488
|
-
} catch {}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
488
|
}
|
|
492
489
|
|
|
493
490
|
rl.close();
|
|
@@ -505,7 +502,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
505
502
|
command: "npx",
|
|
506
503
|
args: [
|
|
507
504
|
"-y", "mcp-remote",
|
|
508
|
-
serverUrl
|
|
505
|
+
`${serverUrl}/mcp`,
|
|
509
506
|
"--header",
|
|
510
507
|
`Authorization: Bearer ${token}`,
|
|
511
508
|
"--header",
|
|
@@ -538,7 +535,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
538
535
|
command: "npx",
|
|
539
536
|
args: [
|
|
540
537
|
"-y", "mcp-remote",
|
|
541
|
-
serverUrl
|
|
538
|
+
`${serverUrl}/mcp`,
|
|
542
539
|
"--header",
|
|
543
540
|
`Authorization: Bearer ${token}`,
|
|
544
541
|
"--header",
|
|
@@ -562,14 +559,8 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
562
559
|
mkdirSync(join(HOME, ".codeium", "windsurf"), { recursive: true });
|
|
563
560
|
writeFileSync(wsPath, JSON.stringify(wsConfig, null, 2) + "\n");
|
|
564
561
|
}
|
|
565
|
-
// Install workflows as slash commands (.windsurf/workflows/) — per-project
|
|
566
|
-
const wsWorkflowDir = join(cwd, ".windsurf", "workflows");
|
|
567
|
-
mkdirSync(wsWorkflowDir, { recursive: true });
|
|
568
|
-
cpSync(join(pluginRoot, "skills", "inbox", "SKILL.md"), join(wsWorkflowDir, "patchcord.md"));
|
|
569
|
-
cpSync(join(pluginRoot, "skills", "wait", "SKILL.md"), join(wsWorkflowDir, "patchcord-wait.md"));
|
|
570
562
|
console.log(`\n ${green}✓${r} Windsurf configured: ${dim}${wsPath}${r}`);
|
|
571
|
-
console.log(` ${
|
|
572
|
-
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}`);
|
|
573
564
|
} else if (isGemini) {
|
|
574
565
|
// Gemini CLI: global only (~/.gemini/settings.json)
|
|
575
566
|
const geminiPath = join(HOME, ".gemini", "settings.json");
|
|
@@ -686,19 +677,18 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
686
677
|
let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
687
678
|
// Remove old patchcord config block if present
|
|
688
679
|
existing = existing.replace(/\[mcp_servers\.patchcord\][^\[]*/s, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
689
|
-
|
|
690
|
-
|
|
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
|
|
691
683
|
const envPath = join(cwd, ".env");
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
691
|
}
|
|
699
|
-
writeFileSync(envPath, envContent);
|
|
700
|
-
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nbearer_token_env_var = "${envName}"\n`;
|
|
701
|
-
writeFileSync(configPath, existing);
|
|
702
692
|
// Slash commands (.codex/prompts/)
|
|
703
693
|
const codexPromptsDir = join(codexDir, "prompts");
|
|
704
694
|
mkdirSync(codexPromptsDir, { recursive: true });
|
|
@@ -737,6 +727,22 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
737
727
|
console.log(`\n ${green}✓${r} Claude Code configured: ${dim}${mcpPath}${r}`);
|
|
738
728
|
}
|
|
739
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
|
+
|
|
740
746
|
const toolName = isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
741
747
|
console.log(`\n${dim}Restart your ${toolName} session, then run:${r} ${bold}inbox()${r}`);
|
|
742
748
|
process.exit(0);
|
|
@@ -777,7 +783,7 @@ if (cmd === "skill") {
|
|
|
777
783
|
const baseUrl = mcpUrl.replace(/\/mcp(\/bearer)?$/, "");
|
|
778
784
|
const token = auth.replace(/^Bearer\s+/, "");
|
|
779
785
|
|
|
780
|
-
if (!baseUrl || !token) {
|
|
786
|
+
if (!baseUrl || !token || !isSafeToken(token)) {
|
|
781
787
|
console.error("Cannot read patchcord URL/token from .mcp.json");
|
|
782
788
|
process.exit(1);
|
|
783
789
|
}
|