patchcord 0.3.46 → 0.3.48
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 +38 -50
- package/package.json +1 -1
package/bin/patchcord.mjs
CHANGED
|
@@ -24,6 +24,18 @@ function isSafeToken(t) {
|
|
|
24
24
|
return /^[A-Za-z0-9_\-=+/.]+$/.test(t) && t.length < 200;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function isSafeUrl(u) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(u);
|
|
30
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
31
|
+
} catch { return false; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isSafeId(s) {
|
|
35
|
+
return /^[A-Za-z0-9_\-]+$/.test(s) && s.length < 100;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
if (cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
28
40
|
console.log(`patchcord — agent messaging for AI coding agents
|
|
29
41
|
|
|
@@ -47,6 +59,16 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
47
59
|
const fullStatusline = flags.includes("--full");
|
|
48
60
|
const { readFileSync, writeFileSync } = await import("fs");
|
|
49
61
|
|
|
62
|
+
function safeReadJson(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
let content = readFileSync(filePath, "utf-8");
|
|
65
|
+
// Strip JSONC comments (Zed, Gemini use JSONC)
|
|
66
|
+
content = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
67
|
+
content = content.replace(/,\s*([}\]])/g, "$1");
|
|
68
|
+
return JSON.parse(content);
|
|
69
|
+
} catch { return null; }
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
console.log(`
|
|
51
73
|
___ ____ ___ ____ _ _ ____ ____ ____ ___
|
|
52
74
|
|__] |__| | | |__| | | | |__/ | \\
|
|
@@ -236,38 +258,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
236
258
|
const toolLabel = isZed ? "Zed" : isWindsurf ? "Windsurf" : "Gemini CLI";
|
|
237
259
|
console.log(`\n ${yellow}Note: ${toolLabel} uses global config — applies to all projects.${r}`);
|
|
238
260
|
} else {
|
|
239
|
-
|
|
240
|
-
const isHome = cwd === HOME || cwd === HOME + "/";
|
|
241
|
-
const hasGit = existsSync(join(cwd, ".git"));
|
|
242
|
-
const hasProjectFile = [
|
|
243
|
-
"package.json", "Cargo.toml", "go.mod", "pyproject.toml", "pom.xml",
|
|
244
|
-
"build.gradle", "Makefile", "CMakeLists.txt", ".sln", "Gemfile",
|
|
245
|
-
"composer.json", "mix.exs", "Pipfile", "requirements.txt", "setup.py",
|
|
246
|
-
].some(f => existsSync(join(cwd, f)));
|
|
247
|
-
const isRoot = cwd === "/" || cwd === "C:\\" || cwd === "C:/";
|
|
248
|
-
const isTmp = cwd.startsWith("/tmp") || cwd.includes("/temp");
|
|
249
|
-
|
|
250
|
-
if (isHome || isRoot) {
|
|
251
|
-
console.log(`\n ${red}⚠ This is your home directory, not a project folder!${r}`);
|
|
252
|
-
console.log(` ${yellow}The config will only work for the project folder where it's created.${r}`);
|
|
253
|
-
console.log(` ${yellow}cd into your project first, then run npx patchcord@latest again.${r}\n`);
|
|
254
|
-
const force = (await ask(` ${dim}Set up here anyway? (y/N):${r} `)).trim().toLowerCase();
|
|
255
|
-
if (force !== "y" && force !== "yes") {
|
|
256
|
-
rl.close();
|
|
257
|
-
process.exit(0);
|
|
258
|
-
}
|
|
259
|
-
} else if (!hasGit && !hasProjectFile && !isTmp) {
|
|
260
|
-
console.log(`\n ${yellow}⚠ This doesn't look like a project folder${r} ${dim}(no .git or project files)${r}`);
|
|
261
|
-
console.log(` ${dim}${cwd}${r}`);
|
|
262
|
-
console.log(` ${dim}Make sure you're in the right folder — the agent only works here.${r}`);
|
|
263
|
-
const proceed = (await ask(` ${dim}Continue? (y/N):${r} `)).trim().toLowerCase();
|
|
264
|
-
if (proceed !== "y" && proceed !== "yes") {
|
|
265
|
-
rl.close();
|
|
266
|
-
process.exit(0);
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
console.log(`\n${dim}Project:${r} ${bold}${cwd}${r}`);
|
|
270
|
-
}
|
|
261
|
+
console.log(`\n${dim}Project:${r} ${bold}${cwd}${r}`);
|
|
271
262
|
}
|
|
272
263
|
|
|
273
264
|
|
|
@@ -341,7 +332,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
341
332
|
const geminiPath = join(HOME, ".gemini", "settings.json");
|
|
342
333
|
if (existsSync(geminiPath)) {
|
|
343
334
|
try {
|
|
344
|
-
const existing =
|
|
335
|
+
const existing = safeReadJson(geminiPath) || {};
|
|
345
336
|
if (existing.mcpServers?.patchcord) {
|
|
346
337
|
console.log(`\n ${yellow}⚠ Gemini CLI already configured${r}`);
|
|
347
338
|
console.log(` ${dim}${geminiPath}${r}`);
|
|
@@ -377,7 +368,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
377
368
|
: join(HOME, ".config", "zed", "settings.json");
|
|
378
369
|
if (existsSync(zedPath)) {
|
|
379
370
|
try {
|
|
380
|
-
const existing =
|
|
371
|
+
const existing = safeReadJson(zedPath) || {};
|
|
381
372
|
if (existing.context_servers?.patchcord) {
|
|
382
373
|
console.log(`\n ${yellow}⚠ Zed already configured${r}`);
|
|
383
374
|
console.log(` ${dim}${zedPath}${r}`);
|
|
@@ -418,7 +409,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
418
409
|
console.log(` ${yellow}This overrides per-project config and causes conflicts.${r}`);
|
|
419
410
|
const cleanGlobal = (await ask(` ${dim}Remove patchcord from global config? (Y/n):${r} `)).trim().toLowerCase();
|
|
420
411
|
if (cleanGlobal !== "n" && cleanGlobal !== "no") {
|
|
421
|
-
const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\][^\
|
|
412
|
+
const cleaned = globalContent.replace(/\[mcp_servers\.patchcord\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
422
413
|
writeFileSync(globalCodexConfig, cleaned + "\n");
|
|
423
414
|
console.log(` ${green}✓${r} Removed from global config`);
|
|
424
415
|
}
|
|
@@ -484,7 +475,14 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
484
475
|
const customUrl = (await ask(`\n${dim}Custom server URL? (y/N):${r} `)).trim().toLowerCase();
|
|
485
476
|
if (customUrl === "y" || customUrl === "yes") {
|
|
486
477
|
const url = (await ask("Server URL: ")).trim();
|
|
487
|
-
if (url)
|
|
478
|
+
if (url) {
|
|
479
|
+
if (!isSafeUrl(url)) {
|
|
480
|
+
console.error("Invalid URL. Must start with https:// or http://");
|
|
481
|
+
rl.close();
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
serverUrl = url;
|
|
485
|
+
}
|
|
488
486
|
}
|
|
489
487
|
|
|
490
488
|
rl.close();
|
|
@@ -564,12 +562,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
564
562
|
} else if (isGemini) {
|
|
565
563
|
// Gemini CLI: global only (~/.gemini/settings.json)
|
|
566
564
|
const geminiPath = join(HOME, ".gemini", "settings.json");
|
|
567
|
-
let geminiSettings = {};
|
|
568
|
-
if (existsSync(geminiPath)) {
|
|
569
|
-
try {
|
|
570
|
-
geminiSettings = JSON.parse(readFileSync(geminiPath, "utf-8"));
|
|
571
|
-
} catch {}
|
|
572
|
-
}
|
|
565
|
+
let geminiSettings = (existsSync(geminiPath) && safeReadJson(geminiPath)) || {};
|
|
573
566
|
if (!geminiSettings.mcpServers) geminiSettings.mcpServers = {};
|
|
574
567
|
geminiSettings.mcpServers.patchcord = {
|
|
575
568
|
httpUrl: `${serverUrl}/mcp`,
|
|
@@ -592,12 +585,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
592
585
|
const zedPath = process.platform === "darwin"
|
|
593
586
|
? join(HOME, "Library", "Application Support", "Zed", "settings.json")
|
|
594
587
|
: join(HOME, ".config", "zed", "settings.json");
|
|
595
|
-
let zedSettings = {};
|
|
596
|
-
if (existsSync(zedPath)) {
|
|
597
|
-
try {
|
|
598
|
-
zedSettings = JSON.parse(readFileSync(zedPath, "utf-8"));
|
|
599
|
-
} catch {}
|
|
600
|
-
}
|
|
588
|
+
let zedSettings = (existsSync(zedPath) && safeReadJson(zedPath)) || {};
|
|
601
589
|
if (!zedSettings.context_servers) zedSettings.context_servers = {};
|
|
602
590
|
zedSettings.context_servers.patchcord = {
|
|
603
591
|
url: `${serverUrl}/mcp`,
|
|
@@ -676,7 +664,7 @@ if (!cmd || cmd === "install" || cmd === "agent") {
|
|
|
676
664
|
const configPath = join(codexDir, "config.toml");
|
|
677
665
|
let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
678
666
|
// Remove old patchcord config block if present
|
|
679
|
-
existing = existing.replace(/\[mcp_servers\.patchcord\][^\
|
|
667
|
+
existing = existing.replace(/\[mcp_servers\.patchcord\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
680
668
|
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord]\nurl = "${serverUrl}/mcp/bearer"\nhttp_headers = { "Authorization" = "Bearer ${token}", "X-Patchcord-Machine" = "${hostname}" }\n`;
|
|
681
669
|
writeFileSync(configPath, existing);
|
|
682
670
|
// Clean up any PATCHCORD_TOKEN we previously wrote to .env
|
|
@@ -799,7 +787,7 @@ if (cmd === "skill") {
|
|
|
799
787
|
}
|
|
800
788
|
} catch {}
|
|
801
789
|
|
|
802
|
-
if (!namespace || !agentId) {
|
|
790
|
+
if (!namespace || !agentId || !isSafeId(namespace) || !isSafeId(agentId)) {
|
|
803
791
|
console.error("Cannot determine agent identity. Check your token.");
|
|
804
792
|
process.exit(1);
|
|
805
793
|
}
|