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.
Files changed (2) hide show
  1. package/bin/patchcord.mjs +40 -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
 
@@ -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(` ${green}✓${r} Workflows installed: ${dim}/patchcord${r}, ${dim}/patchcord-wait${r}`);
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
- // Codex requires bearer_token via env var http_headers not supported for auth
690
- 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
691
683
  const envPath = join(cwd, ".env");
692
- let envContent = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
693
- // Replace or add token in .env
694
- if (envContent.includes(envName)) {
695
- envContent = envContent.replace(new RegExp(`${envName}=.*`), `${envName}=${token}`);
696
- } else {
697
- 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
+ }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.3.44",
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",