patchcord 0.5.55 → 0.5.57

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
@@ -75,9 +75,7 @@ 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>" Propose an update to your whoami
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
83
81
  patchcord subscribe Start the realtime listener
@@ -280,7 +278,8 @@ if (cmd === "whoami") {
280
278
  process.exit(1);
281
279
  }
282
280
 
283
- // --propose "<text>"
281
+ // --propose "<text>" — two-shot gate. First call returns "show_human",
282
+ // second call with identical text returns "applied".
284
283
  const proposeIdx = args.indexOf("--propose");
285
284
  if (proposeIdx !== -1) {
286
285
  const text = (args[proposeIdx + 1] || "").trim();
@@ -288,8 +287,9 @@ if (cmd === "whoami") {
288
287
  console.error("Usage: patchcord whoami --propose \"<text>\"");
289
288
  process.exit(1);
290
289
  }
290
+ // Client-side length check — don't even round-trip if over limit.
291
291
  if (text.length > 300) {
292
- console.error(`Whoami text exceeds 300 characters (got ${text.length}).`);
292
+ console.error(`whoami is ${text.length} characters; limit is 300. trim and retry.`);
293
293
  process.exit(1);
294
294
  }
295
295
  const { status, json, body } = await _httpJSON("POST", `${baseUrl}/api/agent/whoami/propose`, token, { text });
@@ -297,35 +297,25 @@ if (cmd === "whoami") {
297
297
  console.error(`✗ HTTP ${status}: ${(json && json.error) || body}`);
298
298
  process.exit(1);
299
299
  }
300
- console.log(`proposal_id: ${json.proposal_id}`);
301
- console.log(`status: ${json.status}`);
302
- console.log(`awaiting human approval. another agent or the dashboard must approve.`);
303
- process.exit(0);
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);
300
+ if (json.status === "applied") {
301
+ console.log(`✓ whoami applied.`);
302
+ console.log(`self: ${json.whoami}`);
303
+ process.exit(0);
316
304
  }
317
- const { status, json, body } = await _httpJSON(
318
- "POST",
319
- `${baseUrl}/api/agent/whoami/approve`,
320
- token,
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);
305
+ if (json.status === "unchanged") {
306
+ console.log(`unchanged — current whoami already matches.`);
307
+ console.log(`self: ${json.whoami}`);
308
+ process.exit(0);
326
309
  }
327
- console.log(`✓ ${json.status}: ${json.agent} (proposal ${json.proposal_id})`);
328
- process.exit(0);
310
+ if (json.status === "show_human") {
311
+ console.log(`gate: show the diff to the human, get explicit yes, then run the same command again to apply.`);
312
+ console.log();
313
+ console.log(`current: ${json.current || "(empty)"}`);
314
+ console.log(`proposed: ${json.proposed}`);
315
+ process.exit(0);
316
+ }
317
+ console.log(`unexpected response: ${body}`);
318
+ process.exit(1);
329
319
  }
330
320
 
331
321
  // plain whoami (no flags)
@@ -725,18 +715,32 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
725
715
  globalCliInstalled = true;
726
716
  } catch {
727
717
  try {
728
- const stableDir = join(HOME, ".local", "share", "patchcord");
729
- mkdirSync(stableDir, { recursive: true });
730
- cpSync(pluginRoot, stableDir, { recursive: true, force: true });
718
+ const fbStableDir = join(HOME, ".local", "share", "patchcord");
719
+ mkdirSync(fbStableDir, { recursive: true });
720
+ cpSync(pluginRoot, fbStableDir, { recursive: true, force: true });
731
721
  const localBin = join(HOME, ".local", "bin");
732
722
  mkdirSync(localBin, { recursive: true });
733
723
  const wrapper = join(localBin, "patchcord");
734
- writeFileSync(wrapper, `#!/bin/sh\nexec node "${join(stableDir, "bin", "patchcord.mjs")}" "$@"\n`);
724
+ writeFileSync(wrapper, `#!/bin/sh\nexec node "${join(fbStableDir, "bin", "patchcord.mjs")}" "$@"\n`);
735
725
  chmodSync(wrapper, 0o755);
736
726
  globalCliInstalled = true;
737
727
  } catch {}
738
728
  }
739
729
 
730
+ // ── Stable copy for Claude Code plugin marketplace ──────────
731
+ // Claude Code's `plugin marketplace add` stores the literal directory
732
+ // path in its plugin manifest. If we point it at `pluginRoot` (the
733
+ // npx ephemeral cache), that directory gets wiped on the next `npx`
734
+ // invocation of any other package, and every Claude Code session
735
+ // afterward errors: "Plugin directory does not exist: ...".
736
+ // Maintain a stable mirror at ~/.local/share/patchcord/ and point
737
+ // the marketplace there instead.
738
+ const stablePluginDir = join(HOME, ".local", "share", "patchcord");
739
+ try {
740
+ mkdirSync(stablePluginDir, { recursive: true });
741
+ cpSync(pluginRoot, stablePluginDir, { recursive: true, force: true });
742
+ } catch {}
743
+
740
744
  // ── Global setup (silent if nothing changed) ──
741
745
  let globalChanges = [];
742
746
  if (globalCliInstalled) globalChanges.push("Patchcord CLI installed globally");
@@ -750,10 +754,14 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
750
754
  try { rmSync(npmCachePatchcord, { recursive: true, force: true }); } catch {}
751
755
  }
752
756
 
753
- // Always re-add marketplace (copies fresh files from this npx package)
754
- // and install/update plugin. Claude Code's built-in plugin update
755
- // doesn't detect new versions from local sources (#37252).
756
- run(`claude plugin marketplace add "${pluginRoot}"`);
757
+ // Always re-add marketplace (point at the STABLE mirror, not pluginRoot
758
+ // which is the npx ephemeral cache). Claude Code stores the literal
759
+ // path in its plugin manifest pointing at npx cache breaks every
760
+ // future session as soon as that cache dir is swept.
761
+ // Remove any prior marketplace registration first so the path actually
762
+ // updates for users who got the buggy npx-cache path on an earlier install.
763
+ run(`claude plugin marketplace remove patchcord-marketplace`);
764
+ run(`claude plugin marketplace add "${stablePluginDir}"`);
757
765
  const installed = run(`claude plugin list`)?.includes("patchcord");
758
766
  wasPluginInstalled = !!installed;
759
767
  if (installed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.5.55",
3
+ "version": "0.5.57",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "author": "ppravdin",
6
6
  "license": "MIT",
@@ -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 (gated)
123
+ ### Updating your own whoami
124
124
 
125
- 300-char hard limit. Goes through a human-approval flow.
125
+ 300-char hard limit (CLI enforces client-side).
126
126
 
127
- - `patchcord whoami --propose "<text>"` submits a pending proposal. Returns a `proposal_id`. The server also creates a normal patchcord message to `human` showing the diff.
128
- - Another agent (NOT you) or the human via dashboard must approve. Self-approval is blocked server-side — you can propose updates to your own whoami, but you cannot approve them.
129
- - To approve someone else's proposal: `patchcord whoami --approve <proposal-id>`. To reject: `patchcord whoami --reject <id>`.
130
- - The human sees your proposal in their inbox/dashboard. They will approve when ready. Do not retry — proposals are idempotent.
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