patchcord 0.3.77 → 0.3.79
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 +143 -28
- package/package.json +1 -1
- package/scripts/check-inbox.sh +6 -7
- package/scripts/statusline.sh +6 -7
- package/skills/codex/SKILL.md +7 -5
- package/skills/inbox/SKILL.md +7 -6
package/bin/patchcord.mjs
CHANGED
|
@@ -39,7 +39,7 @@ const PROJECT_MARKERS = [
|
|
|
39
39
|
".git", "package.json", "package-lock.json", "Cargo.toml", "go.mod", "go.sum",
|
|
40
40
|
"pyproject.toml", "pom.xml", "build.gradle", "Makefile", "CMakeLists.txt",
|
|
41
41
|
"Gemfile", "composer.json", "mix.exs", "requirements.txt", "setup.py",
|
|
42
|
-
".claude", ".codex", ".cursor", ".vscode",
|
|
42
|
+
".claude", ".codex", ".cursor", ".vscode", ".openclaw",
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
function detectFolder(dir) {
|
|
@@ -258,7 +258,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
258
258
|
|
|
259
259
|
const CLIENT_TYPE_MAP = {
|
|
260
260
|
"claude_code": "1", "codex": "2", "cursor": "3", "windsurf": "4",
|
|
261
|
-
"gemini": "5", "vscode": "6", "zed": "7", "opencode": "8",
|
|
261
|
+
"gemini": "5", "vscode": "6", "zed": "7", "opencode": "8", "openclaw": "9",
|
|
262
262
|
};
|
|
263
263
|
|
|
264
264
|
|
|
@@ -292,9 +292,10 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
292
292
|
console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI`);
|
|
293
293
|
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
294
294
|
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
295
|
-
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode`);
|
|
296
|
+
console.log(` ${cyan}9.${r} OpenClaw\n`);
|
|
297
|
+
choice = (await ask(`${dim}Choose (1-9):${r} `)).trim();
|
|
298
|
+
if (!["1","2","3","4","5","6","7","8","9"].includes(choice)) {
|
|
298
299
|
console.error("Invalid choice.");
|
|
299
300
|
rl.close();
|
|
300
301
|
process.exit(1);
|
|
@@ -346,30 +347,92 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
346
347
|
}
|
|
347
348
|
} catch {}
|
|
348
349
|
}
|
|
350
|
+
if (!existingToken) {
|
|
351
|
+
const openclawJsonPath = join(HOME, ".openclaw", "openclaw.json");
|
|
352
|
+
if (existsSync(openclawJsonPath)) {
|
|
353
|
+
try {
|
|
354
|
+
const oc = JSON.parse(readFileSync(openclawJsonPath, "utf-8"));
|
|
355
|
+
const pt = oc?.mcp?.servers?.patchcord;
|
|
356
|
+
if (pt?.headers?.Authorization) {
|
|
357
|
+
existingToken = pt.headers.Authorization.replace(/^Bearer\s+/i, "");
|
|
358
|
+
existingConfigFile = openclawJsonPath;
|
|
359
|
+
}
|
|
360
|
+
} catch {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
349
363
|
if (existingToken) {
|
|
350
|
-
|
|
364
|
+
// Figure out which tool is already configured
|
|
365
|
+
const existingToolName = existingConfigFile.includes(".codex") ? "Codex"
|
|
366
|
+
: existingConfigFile.includes("openclaw") ? "OpenClaw"
|
|
367
|
+
: existingConfigFile.includes(".cursor") ? "Cursor"
|
|
368
|
+
: existingConfigFile.includes(".vscode") ? "VS Code"
|
|
369
|
+
: "Claude Code";
|
|
370
|
+
|
|
371
|
+
// Validate the existing token to get identity
|
|
372
|
+
let existingIdentity = "";
|
|
373
|
+
const validateResp = run(`curl -sf --max-time 5 -H "Authorization: Bearer ${existingToken}" "${serverUrl}/api/inbox?limit=0&count_only=1"`);
|
|
374
|
+
if (validateResp) {
|
|
375
|
+
try {
|
|
376
|
+
const data = JSON.parse(validateResp);
|
|
377
|
+
existingIdentity = `${data.agent_id}@${data.namespace_id}`;
|
|
378
|
+
} catch {}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const identityStr = existingIdentity ? ` (${bold}${existingIdentity}${r}${dim})` : "";
|
|
382
|
+
console.log(`\n ${dim}${existingToolName} agent is already configured in this project${identityStr}${r}`);
|
|
383
|
+
|
|
384
|
+
if (rl) rl.close();
|
|
351
385
|
const { createInterface: createRLU } = await import("readline");
|
|
352
386
|
const rlU = createRLU({ input: process.stdin, output: process.stdout });
|
|
353
387
|
const askU = (q) => new Promise((resolve) => rlU.question(q, resolve));
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
388
|
+
|
|
389
|
+
const updateAnswer = (await askU(` ${bold}Update ${existingToolName} agent? (Y/n):${r} `)).trim().toLowerCase();
|
|
390
|
+
if (updateAnswer !== "n" && updateAnswer !== "no") {
|
|
357
391
|
token = existingToken;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
choice = CLIENT_TYPE_MAP[clientType] || "";
|
|
366
|
-
console.log(` ${green}✓${r} ${bold}${identity}${r} — token valid`);
|
|
367
|
-
} catch {}
|
|
368
|
-
}
|
|
369
|
-
if (!identity) {
|
|
392
|
+
if (existingIdentity) {
|
|
393
|
+
identity = existingIdentity;
|
|
394
|
+
const vResp = validateResp ? JSON.parse(validateResp) : {};
|
|
395
|
+
clientType = vResp.client_type || "";
|
|
396
|
+
choice = CLIENT_TYPE_MAP[clientType] || "";
|
|
397
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r} — token valid`);
|
|
398
|
+
} else {
|
|
370
399
|
console.log(` ${yellow}⚠${r} Token expired or invalid. Starting fresh setup.`);
|
|
371
400
|
token = "";
|
|
372
401
|
}
|
|
402
|
+
rlU.close();
|
|
403
|
+
} else {
|
|
404
|
+
// Offer to add a different tool
|
|
405
|
+
const addAnswer = (await askU(` ${bold}Add another agent to this project? (y/N):${r} `)).trim().toLowerCase();
|
|
406
|
+
rlU.close();
|
|
407
|
+
if (addAnswer === "y" || addAnswer === "yes") {
|
|
408
|
+
// Use existing token but let user pick a different tool
|
|
409
|
+
token = existingToken;
|
|
410
|
+
if (existingIdentity) {
|
|
411
|
+
identity = existingIdentity;
|
|
412
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
413
|
+
} else {
|
|
414
|
+
console.log(` ${yellow}⚠${r} Token expired or invalid. Starting fresh setup.`);
|
|
415
|
+
token = "";
|
|
416
|
+
}
|
|
417
|
+
// Show tool picker
|
|
418
|
+
if (token) {
|
|
419
|
+
const { createInterface: createRL4 } = await import("readline");
|
|
420
|
+
const rl4 = createRL4({ input: process.stdin, output: process.stdout });
|
|
421
|
+
const ask4 = (q) => new Promise((resolve) => rl4.question(q, resolve));
|
|
422
|
+
console.log(`\n${bold}Which agent are you adding?${r}\n`);
|
|
423
|
+
console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI`);
|
|
424
|
+
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
425
|
+
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
426
|
+
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode`);
|
|
427
|
+
console.log(` ${cyan}9.${r} OpenClaw\n`);
|
|
428
|
+
choice = (await ask4(`${dim}Choose (1-9):${r} `)).trim();
|
|
429
|
+
rl4.close();
|
|
430
|
+
if (!["1","2","3","4","5","6","7","8","9"].includes(choice)) {
|
|
431
|
+
console.error("Invalid choice.");
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
373
436
|
}
|
|
374
437
|
}
|
|
375
438
|
|
|
@@ -519,10 +582,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
519
582
|
console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI`);
|
|
520
583
|
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
521
584
|
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
522
|
-
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode
|
|
523
|
-
|
|
585
|
+
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode`);
|
|
586
|
+
console.log(` ${cyan}9.${r} OpenClaw\n`);
|
|
587
|
+
choice = (await ask3(`${dim}Choose (1-9):${r} `)).trim();
|
|
524
588
|
rl3.close();
|
|
525
|
-
if (!["1","2","3","4","5","6","7","8"].includes(choice)) {
|
|
589
|
+
if (!["1","2","3","4","5","6","7","8","9"].includes(choice)) {
|
|
526
590
|
console.error("Invalid choice.");
|
|
527
591
|
process.exit(1);
|
|
528
592
|
}
|
|
@@ -538,6 +602,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
538
602
|
const isVSCode = choice === "6";
|
|
539
603
|
const isZed = choice === "7";
|
|
540
604
|
const isOpenCode = choice === "8";
|
|
605
|
+
const isOpenClaw = choice === "9";
|
|
541
606
|
|
|
542
607
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
543
608
|
|
|
@@ -665,6 +730,52 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
665
730
|
};
|
|
666
731
|
writeFileSync(ocPath, JSON.stringify(ocConfig, null, 2) + "\n");
|
|
667
732
|
console.log(`\n ${green}✓${r} OpenCode configured: ${dim}${ocPath}${r}`);
|
|
733
|
+
} else if (isOpenClaw) {
|
|
734
|
+
// OpenClaw: global ~/.openclaw/openclaw.json → mcp.servers
|
|
735
|
+
// Try CLI first, fall back to direct file write
|
|
736
|
+
const openclawServerEntry = JSON.stringify({
|
|
737
|
+
url: `${serverUrl}/mcp`,
|
|
738
|
+
transport: "streamable-http",
|
|
739
|
+
headers: {
|
|
740
|
+
Authorization: `Bearer ${token}`,
|
|
741
|
+
"X-Patchcord-Machine": hostname,
|
|
742
|
+
},
|
|
743
|
+
connectionTimeoutMs: 300000,
|
|
744
|
+
});
|
|
745
|
+
const cliResult = run(`openclaw mcp set patchcord '${openclawServerEntry.replace(/'/g, "'\\''")}'`);
|
|
746
|
+
if (cliResult !== null) {
|
|
747
|
+
console.log(`\n ${green}✓${r} OpenClaw configured via CLI: ${dim}openclaw mcp set${r}`);
|
|
748
|
+
} else {
|
|
749
|
+
// CLI not available — write config directly
|
|
750
|
+
const openclawDir = join(HOME, ".openclaw");
|
|
751
|
+
const openclawPath = join(openclawDir, "openclaw.json");
|
|
752
|
+
let openclawConfig = {};
|
|
753
|
+
if (existsSync(openclawPath)) {
|
|
754
|
+
try {
|
|
755
|
+
openclawConfig = JSON.parse(readFileSync(openclawPath, "utf-8"));
|
|
756
|
+
} catch {}
|
|
757
|
+
}
|
|
758
|
+
if (!openclawConfig.mcp) openclawConfig.mcp = {};
|
|
759
|
+
if (!openclawConfig.mcp.servers) openclawConfig.mcp.servers = {};
|
|
760
|
+
openclawConfig.mcp.servers.patchcord = {
|
|
761
|
+
url: `${serverUrl}/mcp`,
|
|
762
|
+
transport: "streamable-http",
|
|
763
|
+
headers: {
|
|
764
|
+
Authorization: `Bearer ${token}`,
|
|
765
|
+
"X-Patchcord-Machine": hostname,
|
|
766
|
+
},
|
|
767
|
+
connectionTimeoutMs: 300000,
|
|
768
|
+
};
|
|
769
|
+
mkdirSync(openclawDir, { recursive: true });
|
|
770
|
+
writeFileSync(openclawPath, JSON.stringify(openclawConfig, null, 2) + "\n");
|
|
771
|
+
console.log(`\n ${green}✓${r} OpenClaw configured: ${dim}${openclawPath}${r}`);
|
|
772
|
+
}
|
|
773
|
+
console.log(` ${yellow}Global config — all OpenClaw channels share this agent.${r}`);
|
|
774
|
+
console.log(` ${dim}Run: openclaw gateway restart${r}`);
|
|
775
|
+
// mcp-remote fallback note for older OpenClaw versions
|
|
776
|
+
console.log(`\n ${dim}If tools don't appear after restart, your OpenClaw may be too old${r}`);
|
|
777
|
+
console.log(` ${dim}for streamable-http. Update to v2026.3.31+ or use mcp-remote:${r}`);
|
|
778
|
+
console.log(` ${dim}openclaw mcp set patchcord '{"command":"npx","args":["mcp-remote","${serverUrl}/mcp","--header","Authorization: Bearer ${token}"],"transport":"stdio"}'${r}`);
|
|
668
779
|
} else if (isVSCode) {
|
|
669
780
|
// VS Code: write .vscode/mcp.json (per-project)
|
|
670
781
|
const vscodeDir = join(cwd, ".vscode");
|
|
@@ -840,7 +951,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
840
951
|
}
|
|
841
952
|
|
|
842
953
|
// Warn about gitignore for per-project configs with tokens
|
|
843
|
-
if (!isWindsurf && !isGemini && !isZed) {
|
|
954
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw) {
|
|
844
955
|
const gitignorePath = join(cwd, ".gitignore");
|
|
845
956
|
const configFile = isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : ".mcp.json";
|
|
846
957
|
let needsWarning = true;
|
|
@@ -855,14 +966,18 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
855
966
|
}
|
|
856
967
|
}
|
|
857
968
|
|
|
858
|
-
const toolName = isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
969
|
+
const toolName = isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
859
970
|
|
|
860
|
-
if (!isWindsurf && !isGemini && !isZed) {
|
|
971
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw) {
|
|
861
972
|
console.log(`\n ${dim}To connect a second agent:${r}`);
|
|
862
973
|
console.log(` ${dim}cd into another project and run${r} ${bold}npx patchcord@latest${r} ${dim}there.${r}`);
|
|
863
974
|
}
|
|
864
975
|
|
|
865
|
-
|
|
976
|
+
if (isOpenClaw) {
|
|
977
|
+
console.log(`\n${dim}Run${r} ${bold}openclaw gateway restart${r}${dim}, then tools will be available in your channels.${r}`);
|
|
978
|
+
} else {
|
|
979
|
+
console.log(`\n${dim}Restart your ${toolName} session, then say:${r} ${bold}check inbox${r}`);
|
|
980
|
+
}
|
|
866
981
|
process.exit(0);
|
|
867
982
|
}
|
|
868
983
|
|
package/package.json
CHANGED
package/scripts/check-inbox.sh
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
find_patchcord_mcp_json() {
|
|
5
|
+
# Only check CWD itself — don't walk up.
|
|
6
|
+
# Walking up leaks agent identity from parent dirs into unrelated projects.
|
|
5
7
|
local dir="${1:-$PWD}"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
fi
|
|
11
|
-
dir=$(dirname "$dir")
|
|
12
|
-
done
|
|
8
|
+
if [ -f "$dir/.mcp.json" ]; then
|
|
9
|
+
printf '%s\n' "$dir/.mcp.json"
|
|
10
|
+
return 0
|
|
11
|
+
fi
|
|
13
12
|
return 1
|
|
14
13
|
}
|
|
15
14
|
|
package/scripts/statusline.sh
CHANGED
|
@@ -13,14 +13,13 @@ for arg in "$@"; do
|
|
|
13
13
|
done
|
|
14
14
|
|
|
15
15
|
find_patchcord_mcp_json() {
|
|
16
|
+
# Only check CWD itself — don't walk up.
|
|
17
|
+
# Walking up leaks agent identity from parent dirs into unrelated projects.
|
|
16
18
|
local dir="$1"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
fi
|
|
22
|
-
dir=$(dirname "$dir")
|
|
23
|
-
done
|
|
19
|
+
if [ -f "$dir/.mcp.json" ]; then
|
|
20
|
+
printf '%s\n' "$dir/.mcp.json"
|
|
21
|
+
return 0
|
|
22
|
+
fi
|
|
24
23
|
return 1
|
|
25
24
|
}
|
|
26
25
|
|
package/skills/codex/SKILL.md
CHANGED
|
@@ -87,16 +87,18 @@ attachment(relay=true, path_or_url="https://example.com/file.md", filename="file
|
|
|
87
87
|
```
|
|
88
88
|
Server fetches the URL and stores it. ~50 tokens instead of thousands for the file content.
|
|
89
89
|
|
|
90
|
-
**Presigned upload (for local files):**
|
|
90
|
+
**Presigned upload (preferred for local files):**
|
|
91
91
|
```
|
|
92
|
-
attachment(upload=true, filename="report.md") -> returns
|
|
92
|
+
attachment(upload=true, filename="report.md") -> returns {url, path}
|
|
93
|
+
curl -X PUT -H "Content-Type: text/markdown" --data-binary @/path/to/report.md "<url>"
|
|
93
94
|
```
|
|
94
|
-
|
|
95
|
+
Then send the `path` to the other agent. No base64, no token waste.
|
|
95
96
|
|
|
96
|
-
**Inline base64
|
|
97
|
+
**Inline base64 (last resort — small generated content only):**
|
|
97
98
|
```
|
|
98
|
-
attachment(upload=true, filename="
|
|
99
|
+
attachment(upload=true, filename="notes.txt", file_data="<base64>")
|
|
99
100
|
```
|
|
101
|
+
Never use for files on disk — use presigned upload above instead.
|
|
100
102
|
|
|
101
103
|
**Downloading:**
|
|
102
104
|
```
|
package/skills/inbox/SKILL.md
CHANGED
|
@@ -75,17 +75,18 @@ attachment(relay=true, path_or_url="https://example.com/file.md", filename="file
|
|
|
75
75
|
```
|
|
76
76
|
Server fetches the URL and stores it. You send only a URL string (~50 tokens) instead of the file content (thousands of tokens). Always prefer relay when the file is at a public URL.
|
|
77
77
|
|
|
78
|
-
**Presigned upload (for local files):**
|
|
78
|
+
**Presigned upload (preferred for local files):**
|
|
79
79
|
```
|
|
80
|
-
attachment(upload=true, filename="report.md") -> returns
|
|
80
|
+
attachment(upload=true, filename="report.md") -> returns {url, path}
|
|
81
|
+
curl -X PUT -H "Content-Type: text/markdown" --data-binary @/path/to/report.md "<url>"
|
|
81
82
|
```
|
|
82
|
-
|
|
83
|
+
Then send the `path` to the other agent. No base64, no token waste.
|
|
83
84
|
|
|
84
|
-
**Inline base64
|
|
85
|
+
**Inline base64 (last resort — small generated content only):**
|
|
85
86
|
```
|
|
86
|
-
attachment(upload=true, filename="
|
|
87
|
+
attachment(upload=true, filename="notes.txt", file_data="<base64>")
|
|
87
88
|
```
|
|
88
|
-
|
|
89
|
+
Base64 adds ~33% overhead and wastes context tokens. Never use this for files on disk — use presigned upload above instead.
|
|
89
90
|
|
|
90
91
|
**Downloading:**
|
|
91
92
|
```
|