patchcord 0.3.76 → 0.3.78
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 +133 -35
- 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,75 @@ 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
|
+
|
|
351
384
|
const { createInterface: createRLU } = await import("readline");
|
|
352
385
|
const rlU = createRLU({ input: process.stdin, output: process.stdout });
|
|
353
386
|
const askU = (q) => new Promise((resolve) => rlU.question(q, resolve));
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (
|
|
387
|
+
|
|
388
|
+
const updateAnswer = (await askU(` ${bold}Update ${existingToolName} agent? (Y/n):${r} `)).trim().toLowerCase();
|
|
389
|
+
if (updateAnswer !== "n" && updateAnswer !== "no") {
|
|
357
390
|
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) {
|
|
391
|
+
if (existingIdentity) {
|
|
392
|
+
identity = existingIdentity;
|
|
393
|
+
const vResp = validateResp ? JSON.parse(validateResp) : {};
|
|
394
|
+
clientType = vResp.client_type || "";
|
|
395
|
+
choice = CLIENT_TYPE_MAP[clientType] || "";
|
|
396
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r} — token valid`);
|
|
397
|
+
} else {
|
|
370
398
|
console.log(` ${yellow}⚠${r} Token expired or invalid. Starting fresh setup.`);
|
|
371
399
|
token = "";
|
|
372
400
|
}
|
|
401
|
+
rlU.close();
|
|
402
|
+
} else {
|
|
403
|
+
// Offer to add a different tool
|
|
404
|
+
const addAnswer = (await askU(` ${bold}Add another agent to this project? (y/N):${r} `)).trim().toLowerCase();
|
|
405
|
+
rlU.close();
|
|
406
|
+
if (addAnswer === "y" || addAnswer === "yes") {
|
|
407
|
+
// Use existing token but let user pick a different tool
|
|
408
|
+
token = existingToken;
|
|
409
|
+
if (existingIdentity) {
|
|
410
|
+
identity = existingIdentity;
|
|
411
|
+
console.log(` ${green}✓${r} ${bold}${identity}${r}`);
|
|
412
|
+
} else {
|
|
413
|
+
console.log(` ${yellow}⚠${r} Token expired or invalid. Starting fresh setup.`);
|
|
414
|
+
token = "";
|
|
415
|
+
}
|
|
416
|
+
// Force tool picker by clearing choice — will be asked below
|
|
417
|
+
choice = "";
|
|
418
|
+
}
|
|
373
419
|
}
|
|
374
420
|
}
|
|
375
421
|
|
|
@@ -519,10 +565,11 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
519
565
|
console.log(` ${cyan}1.${r} Claude Code ${cyan}5.${r} Gemini CLI`);
|
|
520
566
|
console.log(` ${cyan}2.${r} Codex CLI ${cyan}6.${r} VS Code`);
|
|
521
567
|
console.log(` ${cyan}3.${r} Cursor ${cyan}7.${r} Zed`);
|
|
522
|
-
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode
|
|
523
|
-
|
|
568
|
+
console.log(` ${cyan}4.${r} Windsurf ${cyan}8.${r} OpenCode`);
|
|
569
|
+
console.log(` ${cyan}9.${r} OpenClaw\n`);
|
|
570
|
+
choice = (await ask3(`${dim}Choose (1-9):${r} `)).trim();
|
|
524
571
|
rl3.close();
|
|
525
|
-
if (!["1","2","3","4","5","6","7","8"].includes(choice)) {
|
|
572
|
+
if (!["1","2","3","4","5","6","7","8","9"].includes(choice)) {
|
|
526
573
|
console.error("Invalid choice.");
|
|
527
574
|
process.exit(1);
|
|
528
575
|
}
|
|
@@ -538,6 +585,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
538
585
|
const isVSCode = choice === "6";
|
|
539
586
|
const isZed = choice === "7";
|
|
540
587
|
const isOpenCode = choice === "8";
|
|
588
|
+
const isOpenClaw = choice === "9";
|
|
541
589
|
|
|
542
590
|
const hostname = run("hostname -s") || run("hostname") || "unknown";
|
|
543
591
|
|
|
@@ -549,7 +597,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
549
597
|
const cursorConfig = {
|
|
550
598
|
mcpServers: {
|
|
551
599
|
patchcord: {
|
|
552
|
-
url: `${serverUrl}/mcp
|
|
600
|
+
url: `${serverUrl}/mcp`,
|
|
553
601
|
headers: {
|
|
554
602
|
Authorization: `Bearer ${token}`,
|
|
555
603
|
"X-Patchcord-Machine": hostname,
|
|
@@ -578,7 +626,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
578
626
|
const wsConfig = {
|
|
579
627
|
mcpServers: {
|
|
580
628
|
patchcord: {
|
|
581
|
-
url: `${serverUrl}/mcp
|
|
629
|
+
url: `${serverUrl}/mcp`,
|
|
582
630
|
headers: {
|
|
583
631
|
Authorization: `Bearer ${token}`,
|
|
584
632
|
"X-Patchcord-Machine": hostname,
|
|
@@ -609,7 +657,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
609
657
|
let geminiSettings = (existsSync(geminiPath) && safeReadJson(geminiPath)) || {};
|
|
610
658
|
if (!geminiSettings.mcpServers) geminiSettings.mcpServers = {};
|
|
611
659
|
geminiSettings.mcpServers.patchcord = {
|
|
612
|
-
httpUrl: `${serverUrl}/mcp
|
|
660
|
+
httpUrl: `${serverUrl}/mcp`,
|
|
613
661
|
headers: {
|
|
614
662
|
Authorization: `Bearer ${token}`,
|
|
615
663
|
"X-Patchcord-Machine": hostname,
|
|
@@ -632,7 +680,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
632
680
|
let zedSettings = (existsSync(zedPath) && safeReadJson(zedPath)) || {};
|
|
633
681
|
if (!zedSettings.context_servers) zedSettings.context_servers = {};
|
|
634
682
|
zedSettings.context_servers.patchcord = {
|
|
635
|
-
url: `${serverUrl}/mcp
|
|
683
|
+
url: `${serverUrl}/mcp`,
|
|
636
684
|
headers: {
|
|
637
685
|
Authorization: `Bearer ${token}`,
|
|
638
686
|
"X-Patchcord-Machine": hostname,
|
|
@@ -657,7 +705,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
657
705
|
if (!ocConfig.mcp) ocConfig.mcp = {};
|
|
658
706
|
ocConfig.mcp.patchcord = {
|
|
659
707
|
type: "remote",
|
|
660
|
-
url: `${serverUrl}/mcp
|
|
708
|
+
url: `${serverUrl}/mcp`,
|
|
661
709
|
headers: {
|
|
662
710
|
Authorization: `Bearer ${token}`,
|
|
663
711
|
"X-Patchcord-Machine": hostname,
|
|
@@ -665,6 +713,52 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
665
713
|
};
|
|
666
714
|
writeFileSync(ocPath, JSON.stringify(ocConfig, null, 2) + "\n");
|
|
667
715
|
console.log(`\n ${green}✓${r} OpenCode configured: ${dim}${ocPath}${r}`);
|
|
716
|
+
} else if (isOpenClaw) {
|
|
717
|
+
// OpenClaw: global ~/.openclaw/openclaw.json → mcp.servers
|
|
718
|
+
// Try CLI first, fall back to direct file write
|
|
719
|
+
const openclawServerEntry = JSON.stringify({
|
|
720
|
+
url: `${serverUrl}/mcp`,
|
|
721
|
+
transport: "streamable-http",
|
|
722
|
+
headers: {
|
|
723
|
+
Authorization: `Bearer ${token}`,
|
|
724
|
+
"X-Patchcord-Machine": hostname,
|
|
725
|
+
},
|
|
726
|
+
connectionTimeoutMs: 300000,
|
|
727
|
+
});
|
|
728
|
+
const cliResult = run(`openclaw mcp set patchcord '${openclawServerEntry.replace(/'/g, "'\\''")}'`);
|
|
729
|
+
if (cliResult !== null) {
|
|
730
|
+
console.log(`\n ${green}✓${r} OpenClaw configured via CLI: ${dim}openclaw mcp set${r}`);
|
|
731
|
+
} else {
|
|
732
|
+
// CLI not available — write config directly
|
|
733
|
+
const openclawDir = join(HOME, ".openclaw");
|
|
734
|
+
const openclawPath = join(openclawDir, "openclaw.json");
|
|
735
|
+
let openclawConfig = {};
|
|
736
|
+
if (existsSync(openclawPath)) {
|
|
737
|
+
try {
|
|
738
|
+
openclawConfig = JSON.parse(readFileSync(openclawPath, "utf-8"));
|
|
739
|
+
} catch {}
|
|
740
|
+
}
|
|
741
|
+
if (!openclawConfig.mcp) openclawConfig.mcp = {};
|
|
742
|
+
if (!openclawConfig.mcp.servers) openclawConfig.mcp.servers = {};
|
|
743
|
+
openclawConfig.mcp.servers.patchcord = {
|
|
744
|
+
url: `${serverUrl}/mcp`,
|
|
745
|
+
transport: "streamable-http",
|
|
746
|
+
headers: {
|
|
747
|
+
Authorization: `Bearer ${token}`,
|
|
748
|
+
"X-Patchcord-Machine": hostname,
|
|
749
|
+
},
|
|
750
|
+
connectionTimeoutMs: 300000,
|
|
751
|
+
};
|
|
752
|
+
mkdirSync(openclawDir, { recursive: true });
|
|
753
|
+
writeFileSync(openclawPath, JSON.stringify(openclawConfig, null, 2) + "\n");
|
|
754
|
+
console.log(`\n ${green}✓${r} OpenClaw configured: ${dim}${openclawPath}${r}`);
|
|
755
|
+
}
|
|
756
|
+
console.log(` ${yellow}Global config — all OpenClaw channels share this agent.${r}`);
|
|
757
|
+
console.log(` ${dim}Run: openclaw gateway restart${r}`);
|
|
758
|
+
// mcp-remote fallback note for older OpenClaw versions
|
|
759
|
+
console.log(`\n ${dim}If tools don't appear after restart, your OpenClaw may be too old${r}`);
|
|
760
|
+
console.log(` ${dim}for streamable-http. Update to v2026.3.31+ or use mcp-remote:${r}`);
|
|
761
|
+
console.log(` ${dim}openclaw mcp set patchcord '{"command":"npx","args":["mcp-remote","${serverUrl}/mcp","--header","Authorization: Bearer ${token}"],"transport":"stdio"}'${r}`);
|
|
668
762
|
} else if (isVSCode) {
|
|
669
763
|
// VS Code: write .vscode/mcp.json (per-project)
|
|
670
764
|
const vscodeDir = join(cwd, ".vscode");
|
|
@@ -674,7 +768,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
674
768
|
servers: {
|
|
675
769
|
patchcord: {
|
|
676
770
|
type: "http",
|
|
677
|
-
url: `${serverUrl}/mcp
|
|
771
|
+
url: `${serverUrl}/mcp`,
|
|
678
772
|
headers: {
|
|
679
773
|
Authorization: `Bearer ${token}`,
|
|
680
774
|
"X-Patchcord-Machine": hostname,
|
|
@@ -715,7 +809,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
715
809
|
let existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
716
810
|
// Remove old patchcord config block if present
|
|
717
811
|
existing = existing.replace(/\[mcp_servers\.patchcord[-\w]*\]\n(?:(?!\[)[^\n]*\n?)*/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
718
|
-
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord-codex]\nurl = "${serverUrl}/mcp
|
|
812
|
+
existing = existing.trimEnd() + `\n\n[mcp_servers.patchcord-codex]\nurl = "${serverUrl}/mcp"\nhttp_headers = { "Authorization" = "Bearer ${token}", "X-Patchcord-Machine" = "${hostname}" }\ntool_timeout_sec = 300\n`;
|
|
719
813
|
writeFileSync(configPath, existing);
|
|
720
814
|
// Clean up any PATCHCORD_TOKEN we previously wrote to .env
|
|
721
815
|
const envPath = join(cwd, ".env");
|
|
@@ -840,7 +934,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
840
934
|
}
|
|
841
935
|
|
|
842
936
|
// Warn about gitignore for per-project configs with tokens
|
|
843
|
-
if (!isWindsurf && !isGemini && !isZed) {
|
|
937
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw) {
|
|
844
938
|
const gitignorePath = join(cwd, ".gitignore");
|
|
845
939
|
const configFile = isCodex ? ".codex/config.toml" : isCursor ? ".cursor/mcp.json" : isVSCode ? ".vscode/mcp.json" : isOpenCode ? "opencode.json" : ".mcp.json";
|
|
846
940
|
let needsWarning = true;
|
|
@@ -855,14 +949,18 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd === "--token" || cmd ===
|
|
|
855
949
|
}
|
|
856
950
|
}
|
|
857
951
|
|
|
858
|
-
const toolName = isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
952
|
+
const toolName = isOpenClaw ? "OpenClaw" : isOpenCode ? "OpenCode" : isZed ? "Zed" : isVSCode ? "VS Code" : isGemini ? "Gemini CLI" : isWindsurf ? "Windsurf" : isCursor ? "Cursor" : isCodex ? "Codex" : "Claude Code";
|
|
859
953
|
|
|
860
|
-
if (!isWindsurf && !isGemini && !isZed) {
|
|
954
|
+
if (!isWindsurf && !isGemini && !isZed && !isOpenClaw) {
|
|
861
955
|
console.log(`\n ${dim}To connect a second agent:${r}`);
|
|
862
956
|
console.log(` ${dim}cd into another project and run${r} ${bold}npx patchcord@latest${r} ${dim}there.${r}`);
|
|
863
957
|
}
|
|
864
958
|
|
|
865
|
-
|
|
959
|
+
if (isOpenClaw) {
|
|
960
|
+
console.log(`\n${dim}Run${r} ${bold}openclaw gateway restart${r}${dim}, then tools will be available in your channels.${r}`);
|
|
961
|
+
} else {
|
|
962
|
+
console.log(`\n${dim}Restart your ${toolName} session, then say:${r} ${bold}check inbox${r}`);
|
|
963
|
+
}
|
|
866
964
|
process.exit(0);
|
|
867
965
|
}
|
|
868
966
|
|
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
|
```
|