patchcord 0.6.40 → 0.6.41

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.40",
4
+ "version": "0.6.41",
5
5
  "author": {
6
6
  "name": "ppravdin"
7
7
  },
package/bin/patchcord.mjs CHANGED
@@ -993,7 +993,27 @@ if (cmd === "subscribe") {
993
993
  spawnEnv.PATCHCORD_BEARER_TOKEN = bearerInfo.token;
994
994
  }
995
995
  const { spawnSync } = await import("child_process");
996
- const result = spawnSync(process.execPath, [subscribeScript], {
996
+ // FORWARD argv. This used to spawn [subscribeScript] with no arguments, so
997
+ // every flag a user typed was silently thrown away between the wrapper and
998
+ // the script that reads it.
999
+ //
1000
+ // What that broke: subscribe.mjs gates its Hermes webhook bridge on
1001
+ // `process.argv.includes("--hermes")` — its OWN argv. Our Hermes skill tells
1002
+ // the human to run `PATCHCORD_HERMES_WEBHOOK=<url> patchcord subscribe
1003
+ // --hermes`. The env var arrived, the flag did not, and the flag is the gate.
1004
+ // notify() then took the `!HERMES_MODE` branch and wrote to a stdout no
1005
+ // Hermes gateway reads. The listener ran. Nothing was ever woken. No error.
1006
+ // The `--hermes <url>` inline form was worse: it has no env fallback at all.
1007
+ //
1008
+ // Forward everything rather than allow-listing `--hermes`. An allow-list
1009
+ // fixes today and leaves the trap armed: the next person adds a child-side
1010
+ // flag, reads subscribe.mjs, sees it handled, and never looks at these eight
1011
+ // lines. Same silent failure, new flag name. Reported by lead@mux-v2, who
1012
+ // asked for the fix in this shape rather than the narrow one.
1013
+ //
1014
+ // `--kimi` never hit this because the kimi branch above parses it here in the
1015
+ // parent and re-passes the interval explicitly. That was luck, not design.
1016
+ const result = spawnSync(process.execPath, [subscribeScript, ...args], {
997
1017
  stdio: "inherit",
998
1018
  env: spawnEnv,
999
1019
  });
@@ -4037,21 +4057,71 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
4037
4057
  console.log(` ${green}✓${r} Cleaned PATCHCORD_TOKEN from .env`);
4038
4058
  }
4039
4059
  }
4040
- // Clean up old per-project slash commands (deprecated in Codex v0.117+)
4041
- for (const dir of [join(codexDir, "prompts"), join(homedir(), ".codex", "prompts")]) {
4042
- for (const f of ["patchcord.md", "patchcord-wait.md"]) {
4043
- const p = join(dir, f);
4044
- if (existsSync(p)) { unlinkSync(p); }
4045
- }
4046
- }
4060
+ // We do NOT touch .codex/prompts/ — not the project one, not the home one.
4061
+ //
4062
+ // We used to write /patchcord and /patchcord-wait there, then a later commit
4063
+ // deleted them on every install. Both halves were wrong to keep.
4064
+ //
4065
+ // Writing them is pointless: Codex stopped loading $CODEX_HOME/prompts
4066
+ // entirely. Verified on 0.145 — a probe file at ~/.codex/prompts/pctest.md
4067
+ // and at ./.codex/prompts/pcproj.md both give "no matches" in the slash
4068
+ // menu, while /us still filters to /usage, so the menu itself works.
4069
+ // OpenAI marks custom prompts deprecated and says "use skills instead";
4070
+ // openai/codex#15941 reports the 0.117 break and was closed "not planned".
4071
+ //
4072
+ // Deleting them is worse than pointless. A user still on Codex < 0.117 has
4073
+ // WORKING slash commands, and the delete loop took them away on the next
4074
+ // install. It gave that user nothing back. `@patchcord` is the replacement
4075
+ // and it is installed below via the skills path.
4076
+ //
4077
+ // If you are tempted to re-add either half, run the probe first. Do not
4078
+ // trust this comment and do not trust the docs.
4079
+
4047
4080
  // Clean up old per-project skill (now installed as global plugin)
4048
4081
  const oldSkillPath = join(cwd, ".agents", "skills", "patchcord", "SKILL.md");
4049
4082
  if (existsSync(oldSkillPath)) { unlinkSync(oldSkillPath); }
4050
4083
 
4051
- // Install Codex plugin at ~/.agents/plugins/patchcord/ (personal marketplace)
4052
- const pluginVersion = JSON.parse(readFileSync(join(pluginRoot, "package.json"), "utf-8")).version;
4084
+ // Install the Codex plugin.
4085
+ //
4086
+ // TWO DIFFERENT DIRECTORIES, and putting them in one place is the bug this
4087
+ // replaces. The marketplace FILE lives at ~/.agents/plugins/marketplace.json.
4088
+ // The plugin BODY lives at ~/plugins/patchcord. A `source.path` in that file
4089
+ // resolves against HOME — NOT against the directory holding the file.
4090
+ //
4091
+ // We previously wrote the body to ~/.agents/plugins/patchcord and pointed at
4092
+ // "./patchcord". Codex read that as ~/patchcord, which never existed, so:
4093
+ //
4094
+ // $ codex plugin list
4095
+ // patchcord@patchcord not installed /home/now/patchcord
4096
+ //
4097
+ // The plugin has never once installed. Only the ~/.agents/skills/ copy
4098
+ // written further down kept @patchcord alive, which is why nobody noticed.
4099
+ //
4100
+ // The layout below is OpenAI's own, from the bundled plugin-creator skill
4101
+ // (~/.codex/skills/.system/plugin-creator/): the scaffold writes bodies to
4102
+ // ~/plugins/<name>, and its SKILL.md states "In either location, the
4103
+ // generated source path remains ./plugins/<plugin-name>".
4104
+ //
4105
+ // Verify with `codex plugin list` after changing this. STATUS must not say
4106
+ // "not installed", and PATH must be a directory that exists.
4107
+ // Codex caches an installed plugin BY VERSION under ~/.codex/plugins/cache/.
4108
+ // Re-running the installer on the same release therefore reinstalls nothing,
4109
+ // and re-running it is the normal way people pick up a fix. So append the
4110
+ // cachebuster suffix Codex defines for exactly this case — the format is
4111
+ // `+codex.<token>`, per the bundled plugin-creator helper
4112
+ // (~/.codex/skills/.system/plugin-creator/scripts/update_plugin_cachebuster.py).
4113
+ const basePluginVersion = JSON.parse(readFileSync(join(pluginRoot, "package.json"), "utf-8")).version;
4114
+ const pluginVersion = `${basePluginVersion}+codex.${new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14)}`;
4053
4115
  const marketplaceDir = join(homedir(), ".agents", "plugins");
4054
- const pluginDir = join(marketplaceDir, "patchcord");
4116
+ const pluginDir = join(homedir(), "plugins", "patchcord");
4117
+
4118
+ // Remove the body from the old, never-loadable location. Leaving it behind
4119
+ // makes `plugin list` ambiguous and keeps a stale copy of the skills on disk.
4120
+ const deadPluginDir = join(marketplaceDir, "patchcord");
4121
+ if (existsSync(join(deadPluginDir, ".codex-plugin", "plugin.json"))) {
4122
+ try { rmSync(deadPluginDir, { recursive: true, force: true }); } catch {}
4123
+ }
4124
+
4055
4125
  mkdirSync(join(pluginDir, ".codex-plugin"), { recursive: true });
4056
4126
  mkdirSync(join(pluginDir, "skills", "patchcord"), { recursive: true });
4057
4127
  mkdirSync(join(pluginDir, "skills", "patchcord-wait"), { recursive: true });
@@ -4081,7 +4151,7 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
4081
4151
  writeFileSync(join(pluginDir, "skills", "patchcord-wait", "SKILL.md"),
4082
4152
  readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
4083
4153
 
4084
- // Personal marketplace entry (relative path from marketplace root)
4154
+ // Personal marketplace entry. `path` is relative to HOME, not to this file.
4085
4155
  const marketplacePath = join(marketplaceDir, "marketplace.json");
4086
4156
  updateJsonConfig(marketplacePath, (obj) => {
4087
4157
  if (!obj.name) obj.name = "patchcord";
@@ -4089,24 +4159,71 @@ if (!cmd || cmd === "install" || cmd === "agent" || cmd?.startsWith("--")) {
4089
4159
  obj.plugins = (obj.plugins || []).filter(p => p.name !== "patchcord");
4090
4160
  obj.plugins.push({
4091
4161
  name: "patchcord",
4092
- source: { source: "local", path: "./patchcord" },
4162
+ source: { source: "local", path: "./plugins/patchcord" },
4093
4163
  policy: { installation: "INSTALLED_BY_DEFAULT", authentication: "ON_INSTALL" },
4094
4164
  category: "Productivity",
4095
4165
  });
4096
4166
  });
4097
4167
 
4098
- // Also install global skills (working @patchcord plugin/read broken in Codex v0.117)
4168
+ // Writing the marketplace entry does NOT install the plugin. Codex leaves it
4169
+ // at "not installed" until this runs, `INSTALLED_BY_DEFAULT` notwithstanding.
4170
+ // We never ran it, which is the second half of why the plugin was dead.
4171
+ // The command is `plugin add`, not `plugin install` — see
4172
+ // ~/.codex/skills/.system/plugin-creator/references/installing-and-updating.md.
4173
+ //
4174
+ // Not run() — run() has no timeout, and this is the one call here that talks
4175
+ // to another program's install path. The marketplace entry sets
4176
+ // authentication ON_INSTALL, so a future Codex could decide to ask. An
4177
+ // installer that hangs forever is worse than one that skips the plugin, and
4178
+ // skipping is survivable: the ~/.agents/skills/ fallback below covers it.
4179
+ let pluginAdded = false;
4180
+ try {
4181
+ execSync(`codex plugin add patchcord@patchcord`,
4182
+ { stdio: "pipe", encoding: "utf-8", timeout: 30_000 });
4183
+ pluginAdded = true;
4184
+ } catch { pluginAdded = false; }
4185
+
4186
+ // ~/.agents/skills/ holds the SAME two skills as a fallback. Codex reads that
4187
+ // tree directly, with no marketplace and no install step, so @patchcord works
4188
+ // there even when the plugin does not load.
4189
+ //
4190
+ // EXACTLY ONE of the two may exist at a time. Codex reads both and shows both.
4191
+ // With the plugin installed AND the fallback present, `@patchcord` lists:
4192
+ //
4193
+ // Patchcord Cross-machine agent messaging Plugin
4194
+ // patchcord Cross-agent messaging for Codex … Skill
4195
+ // patchcord (patchcord) Cross-agent messaging for Codex … Skill
4196
+ // patchcord-wait Block this turn for up to 5 minutes … Skill
4197
+ // patchcord:wait (patchcord) Block this turn for up to 5 minutes … Skill
4198
+ //
4199
+ // Five entries for two skills. Observed, not predicted — an earlier version
4200
+ // of this comment asserted they would not collide, and that was wrong.
4099
4201
  const globalSkillDir = join(homedir(), ".agents", "skills", "patchcord");
4100
- mkdirSync(globalSkillDir, { recursive: true });
4101
- writeFileSync(join(globalSkillDir, "SKILL.md"),
4102
- readFileSync(join(pluginRoot, "per-project-skills", "codex", "SKILL.md"), "utf-8"));
4103
4202
  const globalWaitDir = join(homedir(), ".agents", "skills", "patchcord-wait");
4104
- mkdirSync(globalWaitDir, { recursive: true });
4105
- writeFileSync(join(globalWaitDir, "SKILL.md"),
4106
- readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
4203
+ if (pluginAdded) {
4204
+ for (const d of [globalSkillDir, globalWaitDir]) {
4205
+ if (existsSync(d)) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
4206
+ }
4207
+ } else {
4208
+ mkdirSync(globalSkillDir, { recursive: true });
4209
+ writeFileSync(join(globalSkillDir, "SKILL.md"),
4210
+ readFileSync(join(pluginRoot, "per-project-skills", "codex", "SKILL.md"), "utf-8"));
4211
+ mkdirSync(globalWaitDir, { recursive: true });
4212
+ writeFileSync(join(globalWaitDir, "SKILL.md"),
4213
+ readFileSync(join(pluginRoot, "skills", "wait", "SKILL.md"), "utf-8"));
4214
+ }
4107
4215
 
4108
4216
  console.log(`\n ${green}✓${r} Codex configured: ${dim}${configPath}${r}`);
4109
- console.log(` ${green}✓${r} Plugin installed: ${dim}@patchcord${r}, ${dim}@patchcord-wait${r}`);
4217
+ if (pluginAdded) {
4218
+ console.log(` ${green}✓${r} Plugin installed: ${dim}${pluginDir}${r}`);
4219
+ } else {
4220
+ // Say it plainly. A silent failure here still leaves @patchcord working
4221
+ // through ~/.agents/skills/, so this is a downgrade, not a breakage.
4222
+ console.log(` ${yellow}!${r} Codex plugin did not install (${dim}codex plugin add patchcord@patchcord${r})`);
4223
+ console.log(` ${dim}Skills still work. Check with: codex plugin list${r}`);
4224
+ }
4225
+ console.log(` ${green}✓${r} Type ${dim}@patchcord${r} or ${dim}@patchcord-wait${r} in Codex`);
4226
+ console.log(` ${dim}Codex dropped custom slash commands — @ is the picker now.${r}`);
4110
4227
  } else {
4111
4228
  // Claude Code: write .mcp.json
4112
4229
  const mcpPath = join(cwd, ".mcp.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchcord",
3
- "version": "0.6.40",
3
+ "version": "0.6.41",
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"