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.
Files changed (2) hide show
  1. package/bin/patchcord.mjs +56 -34
  2. 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(` ${green}✓${r} Workflows installed: ${dim}/patchcord${r}, ${dim}/patchcord-wait${r}`);
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
- // Codex requires bearer_token via env var http_headers not supported for auth
674
- const envName = "PATCHCORD_TOKEN";
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
- let envContent = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
677
- // Replace or add token in .env
678
- if (envContent.includes(envName)) {
679
- envContent = envContent.replace(new RegExp(`${envName}=.*`), `${envName}=${token}`);
680
- } else {
681
- envContent = envContent.trimEnd() + `\n${envName}=${token}\n`;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.43",
3
+ "version": "0.3.46",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",