patchcord 0.6.30 → 0.6.32

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "patchcord",
3
3
  "description": "Cross-machine agent messaging. Messages from other agents land in the inbox and wake the agent to reply.",
4
- "version": "0.6.30",
4
+ "version": "0.6.32",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -196,11 +196,24 @@ team (run from the project root):
196
196
  patchcord team blueprint push --namespace <ns> --file <blueprint.json> [--json]
197
197
  create/update a team blueprint (first push
198
198
  to a new namespace claims it — first-provision)
199
+ PROMOTES BY DEFAULT — the pushed document
200
+ goes live immediately
201
+ patchcord team blueprint push ... --no-promote (alias --dry-run)
202
+ validate + store WITHOUT going live. Use this
203
+ when any deployer may not yet understand a
204
+ value in the document.
199
205
  patchcord team deployment request --namespace <ns> [--revision <uid>] [--json]
200
206
  enqueue an apply of a saved revision
201
207
  (exit 3 = one is already in flight)
202
208
  patchcord team deployment pending [--json] jobs waiting for this host to claim
203
- patchcord team deployment claim --job <uid> [--installation <name>] [--json]
209
+ patchcord team deployment claim --job <uid> [--installation <name>]
210
+ [--installation-uuid <uuid>] [--json]
211
+ --installation is the host LABEL
212
+ (becomes claimed_by).
213
+ --installation-uuid is the identity
214
+ selector; must match the enrolment
215
+ body or identity-key membership is
216
+ never recorded. Optional.
204
217
  take ownership (exit 3 = lost the race)
205
218
  patchcord team deployment complete --job <uid> --status <healthy|degraded|cancelled>
206
219
  [--result <json> | --result-file <path>] [--json]
@@ -1701,13 +1714,27 @@ you design the team, provision its agents, launch them, and manage them.
1701
1714
  const file = flagVal("file");
1702
1715
  const idem = flagVal("idempotency-key");
1703
1716
  const wantJson = process.argv.includes("--json");
1704
- if (!ns || !file) { console.error("Usage: patchcord team blueprint push --namespace <ns> --file <blueprint.json> [--idempotency-key k] [--json]"); process.exit(1); }
1717
+ // --no-promote / --dry-run: store the revision and run strict server
1718
+ // validation WITHOUT moving the live pointer.
1719
+ //
1720
+ // This exists because push promotes by default, so a hand-written
1721
+ // document goes live the instant it validates. That is fine when the
1722
+ // document is one every consumer understands, and it is how a Team gets
1723
+ // bricked when it is not: the server can accept a value that deployers
1724
+ // do not yet handle, and promoting it makes the Team undeployable AND
1725
+ // unrestorable from one command. The rule the team arrived at is
1726
+ // "consumers accept before producers emit", and until now the only way
1727
+ // to honour it here was to remember to. This is the safe path existing
1728
+ // rather than being agreed.
1729
+ const noPromote = process.argv.includes("--no-promote") || process.argv.includes("--dry-run");
1730
+ if (!ns || !file) { console.error("Usage: patchcord team blueprint push --namespace <ns> --file <blueprint.json> [--no-promote|--dry-run] [--idempotency-key k] [--json]"); process.exit(1); }
1705
1731
  let doc;
1706
1732
  try { doc = JSON.parse(readFileSync(file, "utf-8")); }
1707
1733
  catch (e) { console.error(`cannot read blueprint file: ${e.message}`); process.exit(1); }
1708
1734
  // First POST for an unowned namespace IS first-provision (server claims
1709
1735
  // the namespace atomically when PATCHCORD_BLUEPRINT_ATOMIC_CREATE is on).
1710
1736
  const payload = { canonical_json: doc };
1737
+ if (noPromote) payload.promote = false;
1711
1738
  if (idem) payload.idempotency_key = idem;
1712
1739
  const { status, json, body } = await accountCall("POST", `/api/team/${encodeURIComponent(ns)}/blueprint/revisions`, payload);
1713
1740
  if (status !== "201" && status !== "200") {
@@ -1715,7 +1742,7 @@ you design the team, provision its agents, launch them, and manage them.
1715
1742
  process.exit(1);
1716
1743
  }
1717
1744
  if (wantJson) {
1718
- process.stdout.write(JSON.stringify({ ...json, created: status === "201" }) + "\n");
1745
+ process.stdout.write(JSON.stringify({ ...json, created: status === "201", promoted: json.promoted !== false && !noPromote }) + "\n");
1719
1746
  process.exit(0);
1720
1747
  }
1721
1748
  console.log(`${status === "201" ? "created" : "unchanged (dedup)"} revision_uid: ${json.revision_uid}`);
@@ -1785,10 +1812,35 @@ you design the team, provision its agents, launch them, and manage them.
1785
1812
  if (dsub === "claim") {
1786
1813
  const job = flagVal("job");
1787
1814
  const installation = flagVal("installation");
1788
- if (!job) { console.error("Usage: patchcord team deployment claim --job <uid> [--installation <name>] [--json]"); process.exit(1); }
1815
+ // SEPARATE FIELD, deliberately not a new meaning for --installation.
1816
+ //
1817
+ // --installation becomes claimed_by, which is the human-readable "which
1818
+ // host took this job" an operator reads in the deploy list. Putting a
1819
+ // UUID there would spend a label field on an identifier and leave
1820
+ // nothing legible in its place.
1821
+ //
1822
+ // They are also different things by construction: --installation is
1823
+ // <hostname>-<suffix>, live (macOS renames itself on a Bonjour
1824
+ // collision) and MUX_HOST_ID-overridable — the exact selector we
1825
+ // disqualified for the identity flow. --installation-uuid is minted
1826
+ // beside the identity key itself so the key and its selector share one
1827
+ // lifetime. It is what the server keys team membership on, so it must
1828
+ // match the enrolment body's installation_uuid EXACTLY or membership is
1829
+ // written against a string nothing else uses and the team-scoped read
1830
+ // returns an empty set forever.
1831
+ //
1832
+ // Omitting it is legal and stays legal: an installation that has not
1833
+ // upgraded keeps claiming exactly as before and simply records no
1834
+ // identity-key membership. The server has accepted this field since
1835
+ // bb553b2, so consumers accept before producers emit.
1836
+ const installationUuid = flagVal("installation-uuid");
1837
+ if (!job) { console.error("Usage: patchcord team deployment claim --job <uid> [--installation <name>] [--installation-uuid <uuid>] [--json]"); process.exit(1); }
1838
+ const claimBody = {};
1839
+ if (installation) claimBody.installation = installation;
1840
+ if (installationUuid) claimBody.installation_uuid = installationUuid;
1789
1841
  const { status, json, body } = await accountCall(
1790
1842
  "POST", `/api/deployments/${encodeURIComponent(job)}/claim`,
1791
- installation ? { installation } : {},
1843
+ claimBody,
1792
1844
  );
1793
1845
  // Losing a claim race is normal in a multi-host account, not a failure:
1794
1846
  // exit 3 so a poller can skip to the next job without parsing stderr.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.6.30",
3
+ "version": "0.6.32",
4
4
  "description": "Cross-machine agent messaging for Claude Code and Codex",
5
5
  "scripts": {
6
6
  "version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json"
@@ -116,14 +116,18 @@ export function detectClaudeLocalMcpOverride({
116
116
  claude_config: claudeConfig,
117
117
  local_bearer_fp: localFp,
118
118
  disk_bearer_fp: diskFp,
119
+ // Kept SHORT and ending on the action that actually heals it. The old
120
+ // copy ran seven clauses and finished with "restart Claude Code", which
121
+ // is both heavier than needed and incomplete: clearing the entry does
122
+ // nothing until the MCP client reloads, and `/mcp` -> reconnect does that
123
+ // in place. A human who follows the old text to the letter clears the
124
+ // file, sees the tools still 401, and concludes the diagnosis was wrong.
119
125
  tell_human:
120
- "Claude Code cached an old MCP token in ~/.claude.json that overrides this "
121
- + "project's .mcp.json, and the cached copy wins. " + detail + " "
122
- + `Clear projects["${projectDir}"].mcpServers (or just its "${serverKey}" entry) `
123
- + "in ~/.claude.json, then restart Claude Code. "
124
- + "Your Patchcord identity and token are fine only Claude's MCP client is "
125
- + "using the stale copy, which is why the CLI works while the MCP tools 401. "
126
- + "Patchcord will not edit ~/.claude.json for you.",
126
+ `Claude Code cached an old MCP token in ~/.claude.json that overrides this project's .mcp.json. ${detail} `
127
+ + `Remove projects["${projectDir}"].mcpServers["${serverKey}"], then run /mcp and reconnect patchcord `
128
+ + "clearing it alone will not fix it, the MCP client has to reload. "
129
+ + "Your token is fine; only Claude's MCP client holds the stale copy, "
130
+ + "which is why the CLI works while the MCP tools 401.",
127
131
  };
128
132
  } catch {
129
133
  return null;
@@ -69,7 +69,19 @@ patchcord whoami --json
69
69
 
70
70
  If `warnings` contains `claude_local_mcp_cache_override`, **relay its `tell_human` text to the user verbatim.** They have no reason to know this command exists — all they saw was a tool failing.
71
71
 
72
- **Do NOT edit `~/.claude.json` yourself,** and do not ask another agent to. That file holds far more than MCP config, writing it races the running Claude Code process, and clearing an override is the user's call about their own editor. Patchcord diagnoses; the human acts.
72
+ Then **offer to clear it**: *"Want me to remove the stale entry?"* Ask first — `~/.claude.json` is the user's global editor config, holds far more than MCP servers, and writing it races the running Claude Code process. But do not stop at diagnosing. Most users do not want to hand-edit JSON, and an accurate report they cannot act on leaves them exactly as stuck as no report.
73
+
74
+ If they say yes:
75
+
76
+ 1. **Back it up first** — `cp ~/.claude.json ~/.claude.json.bak.$(date +%s)`.
77
+ 2. **Remove only** `projects["<dir>"].mcpServers["patchcord"]`. Not the whole `mcpServers` object, not the project entry, not the file. Parse, delete the one key, write atomically.
78
+ 3. **Then tell them to run `/mcp` and reconnect patchcord.**
79
+
80
+ **Step 3 is the one that actually heals it, and it is the one that gets forgotten.** Clearing the entry changes a file the running MCP client already read; until it reloads, the tools keep 401-ing with the stale token and it looks like the fix failed. `/mcp` → reconnect reloads in place. Never tell them to restart Claude Code — if they are talking to you, they have already restarted, and it would not have helped anyway.
81
+
82
+ You cannot run `/mcp` yourself; it is an interactive command in the user's client. Say the words and let them press it.
83
+
84
+ Do not ask another agent to edit the file for you.
73
85
 
74
86
  ## If `whoami` is clean and the MCP tools STILL 401
75
87