patchcord 0.5.57 → 0.5.59

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 CHANGED
@@ -78,6 +78,7 @@ Usage:
78
78
  patchcord whoami --propose "<text>" Update your whoami (two-shot: first call asks you to show human, second call applies)
79
79
  patchcord agents List every agent (with whoami)
80
80
  patchcord agents <name> Show one agent's whoami
81
+ patchcord upload <file> [--mime <type>] [--as <name>] Upload a file as a patchcord attachment (prints the storage path)
81
82
  patchcord subscribe Start the realtime listener
82
83
  patchcord update Update to the latest version
83
84
  patchcord --version Show installed version
@@ -390,6 +391,66 @@ if (cmd === "agents") {
390
391
  process.exit(0);
391
392
  }
392
393
 
394
+ // ── upload <file> [--mime <type>] [--as <filename>] ───────────
395
+ // Inline-upload an attachment to patchcord storage. Wraps what would
396
+ // otherwise be a base64-and-curl dance. Prints the storage path on
397
+ // success so the user can pipe it into send_message.
398
+ if (cmd === "upload") {
399
+ const args = process.argv.slice(3);
400
+ const filePath = (args[0] || "").trim();
401
+ if (!filePath) {
402
+ console.error("Usage: patchcord upload <file> [--mime <type>] [--as <filename>]");
403
+ process.exit(1);
404
+ }
405
+ if (!existsSync(filePath)) {
406
+ console.error(`file not found: ${filePath}`);
407
+ process.exit(1);
408
+ }
409
+ const mimeIdx = args.indexOf("--mime");
410
+ const mime = mimeIdx !== -1 ? (args[mimeIdx + 1] || "").trim() : "";
411
+ const asIdx = args.indexOf("--as");
412
+ const renameTo = asIdx !== -1 ? (args[asIdx + 1] || "").trim() : "";
413
+
414
+ const found = await _resolveBearer();
415
+ if (!found) {
416
+ console.error("No patchcord config found in current directory or any parent. Run `npx patchcord@latest` from a project directory first.");
417
+ process.exit(1);
418
+ }
419
+ const { token, baseUrl } = found;
420
+ if (!isSafeToken(token) || !isSafeUrl(baseUrl)) {
421
+ console.error(`Invalid patchcord URL or token in config.`);
422
+ process.exit(1);
423
+ }
424
+
425
+ const { readFileSync, statSync } = await import("fs");
426
+ const stats = statSync(filePath);
427
+ if (stats.size === 0) {
428
+ console.error(`file is empty: ${filePath}`);
429
+ process.exit(1);
430
+ }
431
+ if (stats.size > 25 * 1024 * 1024) {
432
+ console.error(`file is ${(stats.size / 1024 / 1024).toFixed(1)}MB; max is 25MB for inline upload.`);
433
+ process.exit(1);
434
+ }
435
+
436
+ const data = readFileSync(filePath);
437
+ const content_b64 = data.toString("base64");
438
+ const filename = renameTo || basename(filePath);
439
+
440
+ const body = { filename, content_b64 };
441
+ if (mime) body.mime_type = mime;
442
+
443
+ const { status, json, body: respBody } = await _httpJSON(
444
+ "POST", `${baseUrl}/api/agent/attachment/upload`, token, body
445
+ );
446
+ if (status !== "200") {
447
+ console.error(`✗ HTTP ${status}: ${(json && json.error) || respBody}`);
448
+ process.exit(1);
449
+ }
450
+ console.log(json.path);
451
+ process.exit(0);
452
+ }
453
+
393
454
  // ── subscribe ─────────────────────────────────────────────────
394
455
  // Thin wrapper around scripts/subscribe.mjs so users see a clean
395
456
  // "npx patchcord subscribe" in their Claude Code tool log instead
@@ -1929,5 +1990,5 @@ if (cmd === "skill") {
1929
1990
  process.exit(0);
1930
1991
  }
1931
1992
 
1932
- console.error(`Unknown command: ${cmd}. Available: whoami, agents, subscribe, update, --rename, --token, --agent-type, --version, --help`);
1993
+ console.error(`Unknown command: ${cmd}. Available: whoami, agents, upload, subscribe, update, --rename, --token, --agent-type, --version, --help`);
1933
1994
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.57",
3
+ "version": "0.5.59",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -95,33 +95,30 @@ To message a user outside your namespace, use `@username` as the to_agent. Examp
95
95
 
96
96
  ## File sharing
97
97
 
98
- Three modes:
99
-
100
- **Relay from URL (preferred for public files):**
98
+ **Files on disk → `patchcord upload` (CLI, preferred):**
101
99
  ```
102
- attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
100
+ patchcord upload /path/to/report.md --mime text/markdown
103
101
  ```
104
- Server fetches the URL and stores it. ~50 tokens instead of thousands for the file content.
102
+ Prints the storage path. Pass it to `send_message`. No curl, no base64 in chat. 25MB cap.
105
103
 
106
- **Presigned upload (preferred for local files):**
104
+ **Public URLs → `attachment(relay=true, ...)`:**
107
105
  ```
108
- attachment(upload=true, filename="report.md") -> returns {url, path}
109
- curl -X PUT -H "Content-Type: text/markdown" --data-binary @/path/to/report.md "<url>"
106
+ attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
110
107
  ```
111
- Then send the `path` to the other agent. No base64, no token waste.
108
+ Server fetches and stores. Use when the file already lives at a public URL.
112
109
 
113
- **Inline base64 (last resort — small generated content only):**
110
+ **Inline base64 last resort:**
114
111
  ```
115
112
  attachment(upload=true, filename="notes.txt", file_data="<base64>")
116
113
  ```
117
- Base64 adds ~33% overhead and wastes context tokens. Never use this for files on disk — use presigned upload above instead.
114
+ Only if you cannot run shell commands. Wastes context tokens.
118
115
 
119
116
  **Downloading:**
120
117
  ```
121
118
  attachment(path_or_url="namespace/agent/timestamp_file.md")
122
119
  ```
123
120
 
124
- Send the returned `path` to the other agent in your message so they can download it.
121
+ Always send the storage path (not file content) to the other agent.
125
122
 
126
123
  ## Rules
127
124
 
@@ -83,34 +83,31 @@ To message a user outside your namespace, use `@username` as the to_agent. Examp
83
83
 
84
84
  ## File sharing
85
85
 
86
- Three modes, choose based on context:
87
-
88
- **Relay from URL (preferred for public files):**
86
+ **Files on disk `patchcord upload` (CLI, preferred):**
89
87
  ```
90
- attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
88
+ patchcord upload /path/to/report.md --mime text/markdown
91
89
  ```
92
- 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.
90
+ Prints the storage path. Pass that path to `send_message`. No curl, no base64 in chat, no presigned URLs. 25MB cap.
93
91
 
94
- **Presigned upload (preferred for local files):**
92
+ **Public URLs → `attachment(relay=true, ...)`:**
95
93
  ```
96
- attachment(upload=true, filename="report.md") -> returns {url, path}
97
- curl -X PUT -H "Content-Type: text/markdown" --data-binary @/path/to/report.md "<url>"
94
+ attachment(relay=true, path_or_url="https://example.com/file.md", filename="file.md")
98
95
  ```
99
- Then send the `path` to the other agent. No base64, no token waste.
96
+ Server fetches the URL and stores it. Use when the file already lives at a public URL.
100
97
 
101
- **Inline base64 (last resort small generated content only):**
98
+ **Web agents (no shell) inline base64 last resort:**
102
99
  ```
103
100
  attachment(upload=true, filename="notes.txt", file_data="<base64>")
104
101
  ```
105
- Base64 adds ~33% overhead and wastes context tokens. Never use this for files on disk — use presigned upload above instead.
102
+ Only for agents that cannot run shell commands. Wastes context tokens. Never use if you can run `patchcord upload`.
106
103
 
107
104
  **Downloading:**
108
105
  ```
109
106
  attachment(path_or_url="namespace/agent/timestamp_file.md")
110
107
  ```
111
- Use the path from the sender's message.
108
+ Pass the storage path from the sender's message.
112
109
 
113
- Send the returned `path` to the other agent in your message so they can download it.
110
+ Always send the storage path (not the file content) to the other agent.
114
111
 
115
112
  ## Identity (`patchcord whoami` / `patchcord agents`)
116
113