patchcord 0.5.56 → 0.5.58
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 +84 -33
- package/package.json +1 -1
- package/skills/inbox/SKILL.md +15 -6
package/bin/patchcord.mjs
CHANGED
|
@@ -75,11 +75,10 @@ Usage:
|
|
|
75
75
|
patchcord --full Same + full statusline
|
|
76
76
|
patchcord --rename <new-name> [--agent-type <type>] Rename this agent
|
|
77
77
|
patchcord whoami Show your identity + project
|
|
78
|
-
patchcord whoami --propose "<text>"
|
|
79
|
-
patchcord whoami --approve <id> Approve a pending proposal
|
|
80
|
-
patchcord whoami --reject <id> Reject a pending proposal
|
|
78
|
+
patchcord whoami --propose "<text>" Update your whoami (two-shot: first call asks you to show human, second call applies)
|
|
81
79
|
patchcord agents List every agent (with whoami)
|
|
82
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)
|
|
83
82
|
patchcord subscribe Start the realtime listener
|
|
84
83
|
patchcord update Update to the latest version
|
|
85
84
|
patchcord --version Show installed version
|
|
@@ -280,7 +279,8 @@ if (cmd === "whoami") {
|
|
|
280
279
|
process.exit(1);
|
|
281
280
|
}
|
|
282
281
|
|
|
283
|
-
// --propose "<text>"
|
|
282
|
+
// --propose "<text>" — two-shot gate. First call returns "show_human",
|
|
283
|
+
// second call with identical text returns "applied".
|
|
284
284
|
const proposeIdx = args.indexOf("--propose");
|
|
285
285
|
if (proposeIdx !== -1) {
|
|
286
286
|
const text = (args[proposeIdx + 1] || "").trim();
|
|
@@ -288,8 +288,9 @@ if (cmd === "whoami") {
|
|
|
288
288
|
console.error("Usage: patchcord whoami --propose \"<text>\"");
|
|
289
289
|
process.exit(1);
|
|
290
290
|
}
|
|
291
|
+
// Client-side length check — don't even round-trip if over limit.
|
|
291
292
|
if (text.length > 300) {
|
|
292
|
-
console.error(`
|
|
293
|
+
console.error(`whoami is ${text.length} characters; limit is 300. trim and retry.`);
|
|
293
294
|
process.exit(1);
|
|
294
295
|
}
|
|
295
296
|
const { status, json, body } = await _httpJSON("POST", `${baseUrl}/api/agent/whoami/propose`, token, { text });
|
|
@@ -297,35 +298,25 @@ if (cmd === "whoami") {
|
|
|
297
298
|
console.error(`✗ HTTP ${status}: ${(json && json.error) || body}`);
|
|
298
299
|
process.exit(1);
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// --approve <id> / --reject <id>
|
|
307
|
-
const approveIdx = args.indexOf("--approve");
|
|
308
|
-
const rejectIdx = args.indexOf("--reject");
|
|
309
|
-
if (approveIdx !== -1 || rejectIdx !== -1) {
|
|
310
|
-
const isApprove = approveIdx !== -1;
|
|
311
|
-
const idx = isApprove ? approveIdx : rejectIdx;
|
|
312
|
-
const id = (args[idx + 1] || "").trim();
|
|
313
|
-
if (!id) {
|
|
314
|
-
console.error(`Usage: patchcord whoami --${isApprove ? "approve" : "reject"} <proposal-id>`);
|
|
315
|
-
process.exit(1);
|
|
301
|
+
if (json.status === "applied") {
|
|
302
|
+
console.log(`✓ whoami applied.`);
|
|
303
|
+
console.log(`self: ${json.whoami}`);
|
|
304
|
+
process.exit(0);
|
|
316
305
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
{ proposal_id: id, action: isApprove ? "approve" : "reject" },
|
|
322
|
-
);
|
|
323
|
-
if (status !== "200") {
|
|
324
|
-
console.error(`✗ HTTP ${status}: ${(json && json.error) || body}`);
|
|
325
|
-
process.exit(1);
|
|
306
|
+
if (json.status === "unchanged") {
|
|
307
|
+
console.log(`unchanged — current whoami already matches.`);
|
|
308
|
+
console.log(`self: ${json.whoami}`);
|
|
309
|
+
process.exit(0);
|
|
326
310
|
}
|
|
327
|
-
|
|
328
|
-
|
|
311
|
+
if (json.status === "show_human") {
|
|
312
|
+
console.log(`gate: show the diff to the human, get explicit yes, then run the same command again to apply.`);
|
|
313
|
+
console.log();
|
|
314
|
+
console.log(`current: ${json.current || "(empty)"}`);
|
|
315
|
+
console.log(`proposed: ${json.proposed}`);
|
|
316
|
+
process.exit(0);
|
|
317
|
+
}
|
|
318
|
+
console.log(`unexpected response: ${body}`);
|
|
319
|
+
process.exit(1);
|
|
329
320
|
}
|
|
330
321
|
|
|
331
322
|
// plain whoami (no flags)
|
|
@@ -400,6 +391,66 @@ if (cmd === "agents") {
|
|
|
400
391
|
process.exit(0);
|
|
401
392
|
}
|
|
402
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
|
+
|
|
403
454
|
// ── subscribe ─────────────────────────────────────────────────
|
|
404
455
|
// Thin wrapper around scripts/subscribe.mjs so users see a clean
|
|
405
456
|
// "npx patchcord subscribe" in their Claude Code tool log instead
|
|
@@ -1939,5 +1990,5 @@ if (cmd === "skill") {
|
|
|
1939
1990
|
process.exit(0);
|
|
1940
1991
|
}
|
|
1941
1992
|
|
|
1942
|
-
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`);
|
|
1943
1994
|
process.exit(1);
|
package/package.json
CHANGED
package/skills/inbox/SKILL.md
CHANGED
|
@@ -120,14 +120,23 @@ Send the returned `path` to the other agent in your message so they can download
|
|
|
120
120
|
- **Run `patchcord agents`** to see the full roster (every peer's whoami). One call, ~3KB, complete picture of the namespace.
|
|
121
121
|
- **Run `patchcord agents <name>`** when an unknown agent messages you and you want to know who they are before acting on their request.
|
|
122
122
|
|
|
123
|
-
### Updating your own whoami
|
|
123
|
+
### Updating your own whoami
|
|
124
124
|
|
|
125
|
-
300-char hard limit
|
|
125
|
+
300-char hard limit (CLI enforces client-side).
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
127
|
+
Server responds with one of three statuses:
|
|
128
|
+
|
|
129
|
+
- **applied** — done. Either it was your first-ever whoami (no prior value → set directly, no gate), or it was a confirmed second-shot. Print and move on.
|
|
130
|
+
- **unchanged** — proposed text matches current. No-op.
|
|
131
|
+
- **show_human** — current value exists and the proposed text differs. Server printed `current:` and `proposed:`. You MUST:
|
|
132
|
+
1. Show the diff to the human in conversation
|
|
133
|
+
2. Ask them to confirm
|
|
134
|
+
3. Wait for explicit "yes"
|
|
135
|
+
4. Run the **exact same** `patchcord whoami --propose "<text>"` command again. Server will then return `applied`.
|
|
136
|
+
|
|
137
|
+
Pending state expires after 10 minutes. If the human says no, do not call again. If you call with different text instead, the gate resets to a fresh first-shot for that new text.
|
|
138
|
+
|
|
139
|
+
Never call `--propose` a second time with the same text without showing the human between calls.
|
|
131
140
|
|
|
132
141
|
### Hard rules
|
|
133
142
|
|