claude-codex-pair 0.4.0 → 0.4.1

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/DESIGN.md CHANGED
@@ -86,7 +86,9 @@ at most once a day, cache the answer under the state directory, and stay quiet o
86
86
  to stderr after the command's own output, so `pair wait`'s first line is still the delivery or
87
87
  timeout line agents read. `pair upgrade` installs with npm, then runs the `init` of the package it
88
88
  just installed (found with `npm root --global`), not the first `pair` on PATH, because both this
89
- process and an older copy on PATH would write the old templates.
89
+ process and an older copy on PATH would write the old templates. It asks `npm view` first, so it uses
90
+ the same registry as the install. Already on the latest version, it only runs the in-process
91
+ refresh. If the check fails, it goes ahead with the install, which reports the actual npm error.
90
92
 
91
93
  Channel operations report `EACCES` and `EPERM` with the affected path and the sandbox
92
94
  configuration printed by `init`. Either ordinary permissions or sandbox restrictions can
package/README.md CHANGED
@@ -203,8 +203,10 @@ offline or sandboxed.
203
203
  ## Upgrading
204
204
 
205
205
  Run `pair upgrade` in your own terminal, not through an agent: agent sandboxes block the network and
206
- global installs. It runs `npm install -g claude-codex-pair@latest`, then the new `pair init` in the
207
- current worktree if pair is set up there. Other worktrees refresh their instructions on the next
206
+ global installs. If npm has a newer version, it runs `npm install -g claude-codex-pair@latest`, then
207
+ the new `pair init` in the current worktree if pair is set up there, and prints only the files that
208
+ `init` wrote or kept. Already on the latest version, it installs nothing and only refreshes this
209
+ worktree's stale instructions. Other worktrees refresh their instructions on the next
208
210
  `pair send`, `wait`, `history` or `clean`. pair owns these files, so edits inside them are
209
211
  overwritten. pair, including `pair init`, never rewrites instructions that a newer pair wrote, so
210
212
  worktrees shared by different versions don't flip back and forth. To downgrade, delete them first. Restart Claude Code and Codex afterwards. Running
package/dist/pair.js CHANGED
@@ -892,24 +892,31 @@ function status(channel, root) {
892
892
  * Returns a note for after the command's output: agents read `pair wait`'s first line, with stderr merged.
893
893
  */
894
894
  function refreshInstructions(root) {
895
- const skillPath = join(root, ".claude", "skills", "pair", "SKILL.md");
896
- const agentsPath = join(root, "AGENTS.md");
897
- const refreshed = [];
898
895
  try {
899
- if (instructionState(skillPath, claudeSkill()) === "stale") {
900
- writeFileSync(skillPath, claudeSkill());
901
- refreshed.push(skillPath);
902
- }
903
- if (instructionState(agentsPath, codexSection(), true) === "stale") {
904
- writeFileSync(agentsPath, readFileSync(agentsPath, "utf8").replace(PAIR_SECTION, () => codexSection()));
905
- refreshed.push(`${agentsPath} (pair section)`);
906
- }
896
+ return refreshedNote(refreshStale(root));
907
897
  }
908
898
  catch (error) {
909
899
  // A read-only sandbox: message delivery must still work, so report and go on.
910
900
  const reason = error instanceof Error ? error.message : String(error);
911
901
  return `pair: couldn't refresh instructions (${reason}); run \`pair init\` in your own terminal`;
912
902
  }
903
+ }
904
+ /** The stale instruction files it rewrote. A write failure throws. */
905
+ function refreshStale(root) {
906
+ const skillPath = join(root, ".claude", "skills", "pair", "SKILL.md");
907
+ const agentsPath = join(root, "AGENTS.md");
908
+ const refreshed = [];
909
+ if (instructionState(skillPath, claudeSkill()) === "stale") {
910
+ writeFileSync(skillPath, claudeSkill());
911
+ refreshed.push(skillPath);
912
+ }
913
+ if (instructionState(agentsPath, codexSection(), true) === "stale") {
914
+ writeFileSync(agentsPath, readFileSync(agentsPath, "utf8").replace(PAIR_SECTION, () => codexSection()));
915
+ refreshed.push(`${agentsPath} (pair section)`);
916
+ }
917
+ return refreshed;
918
+ }
919
+ function refreshedNote(refreshed) {
913
920
  return refreshed.length > 0
914
921
  ? `pair: refreshed ${refreshed.join(", ")} to pair ${PACKAGE.version}; reload agent sessions to load them`
915
922
  : undefined;
@@ -964,6 +971,21 @@ async function updateNotice() {
964
971
  }
965
972
  /** Installs the latest pair, then lets the new version refresh this worktree's instructions. */
966
973
  function upgrade(root) {
974
+ // npm's own answer, from the registry it installs from; if it fails, the install reports why.
975
+ const view = spawnSync("npm", ["view", PACKAGE.name, "version"], { encoding: "utf8" });
976
+ const latest = view.status === 0 ? view.stdout.trim() : undefined;
977
+ if (latest && !isNewer(latest, PACKAGE.version)) {
978
+ let note;
979
+ try {
980
+ note = refreshedNote(refreshStale(root)); // refreshing is all that is left to do, so a failure fails upgrade
981
+ }
982
+ catch (error) {
983
+ const reason = error instanceof Error ? error.message : String(error);
984
+ throw new PairError(`already up to date, but couldn't refresh instructions (${reason})`);
985
+ }
986
+ console.log(`already up to date: ${PACKAGE.name} ${PACKAGE.version}${note ? `\n${note}` : ""}`);
987
+ return 0;
988
+ }
967
989
  const install = spawnSync("npm", ["install", "--global", `${PACKAGE.name}@latest`], { stdio: "inherit" });
968
990
  if (install.error) {
969
991
  throw new PairError(`couldn't run npm: ${install.error.message}`);
@@ -982,12 +1004,14 @@ function upgrade(root) {
982
1004
  throw new PairError("installed, but `npm root --global` failed; run `pair init` yourself");
983
1005
  }
984
1006
  const installed = join(globalRoot.stdout.trim(), PACKAGE.name, "dist", "pair.js");
985
- const init = spawnSync(process.execPath, [installed, "init"], { cwd: root, stdio: "inherit" });
1007
+ const init = spawnSync(process.execPath, [installed, "init"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
986
1008
  if (init.error || init.status !== 0) {
987
1009
  throw new PairError(`installed, but the new \`pair init\` failed${init.error ? `: ${init.error.message}` : ""}; run it yourself`);
988
1010
  }
1011
+ // Only what it did to the files: the sandbox and `codex mcp add` setup it also prints is already done.
1012
+ console.log(init.stdout.split("\n").filter((line) => /^(?:wrote|kept): /.test(line)).join("\n"));
989
1013
  }
990
- console.log("Restart Claude Code and Codex sessions to load the new version. Other worktrees refresh on their next pair send or wait.");
1014
+ console.log(`upgraded: ${PACKAGE.name} ${PACKAGE.version} → ${latest ?? "latest"}. Restart Claude Code and Codex sessions to load it; other worktrees refresh on their next pair send or wait.`);
991
1015
  return 0;
992
1016
  }
993
1017
  /** Finished threads, most recently finished first. Entries that aren't archived threads are ignored. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-codex-pair",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Claude Code and Codex review each other's work over a file-system chat channel",
5
5
  "keywords": [
6
6
  "claude-code",