pytxo 1.3.6 → 2.0.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/README.md +64 -6
- package/bin/pytxo.js +14 -2
- package/lib/platform.js +2 -0
- package/package.json +12 -7
- package/postinstall.js +10 -3
package/README.md
CHANGED
|
@@ -1,12 +1,70 @@
|
|
|
1
|
-
# pytxo
|
|
1
|
+
# pytxo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Land all your agents' branches in one command.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Pytxo merges your Claude Code, Codex and Copilot branches in a throwaway
|
|
6
|
+
worktree, runs your tests on the combined result, and lands what passes. What breaks stays out, and you see why. Free,
|
|
7
|
+
open source, runs on your machine or in CI.
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
|
|
9
|
-
pytxo
|
|
10
|
+
npx pytxo demo # watch it catch two branches that pass alone and fail together
|
|
11
|
+
npx pytxo # check the agent branches in your repository
|
|
12
|
+
npx pytxo --apply # and land them if they pass
|
|
10
13
|
```
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
`npx pytxo` finds the agent branches (`claude/*`, `codex/*`, `copilot/*`,
|
|
16
|
+
`cursor/*`, ... and every branch in a linked worktree), finds your test command
|
|
17
|
+
(`npm test`, `pytest`, `cargo test`, `go test`, ...), and prints a verdict:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
BLOCKED claude/rename-price and codex/add-discount pass npm test alone but fail together.
|
|
21
|
+
why claude/rename-price renamed price to unitPrice; codex/add-discount still uses price (src/discount.js:1).
|
|
22
|
+
next rebase codex/add-discount onto claude/rename-price and use unitPrice, then run npx pytxo again.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Nothing in your repository changes unless you add `--apply`. Exit codes: `0` ready or landed (or
|
|
26
|
+
nothing to check), `2` blocked or `--apply` landed only some branches, `3` stale (the target moved),
|
|
27
|
+
`1` error, `130` stopped by Ctrl+C.
|
|
28
|
+
|
|
29
|
+
In CI, one line in a workflow checks each agent pull request together with
|
|
30
|
+
the other open ones into its base branch. Add
|
|
31
|
+
`.github/workflows/pytxo.yml`:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
name: Pytxo
|
|
35
|
+
on: pull_request
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
pull-requests: write
|
|
39
|
+
concurrency:
|
|
40
|
+
group: pytxo-${{ github.event.pull_request.number }}
|
|
41
|
+
cancel-in-progress: true
|
|
42
|
+
jobs:
|
|
43
|
+
check:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: Pytxo-dev/pytxo/actions/land@v2
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Your tests run in the job that holds this token, so give the workflow no other
|
|
50
|
+
permissions or secrets. If Copilot's coding agent opens pull requests here, add
|
|
51
|
+
`actions: read` so the check can include them once someone lets their run
|
|
52
|
+
start; with it, code in the checked branches can also read every workflow
|
|
53
|
+
run's logs and artifacts. See
|
|
54
|
+
[the GitHub Action guide](https://pytxo.com/docs/getting-started/land-in-ci).
|
|
55
|
+
|
|
56
|
+
Docs: [pytxo.com/docs](https://pytxo.com/docs) · Source:
|
|
57
|
+
[github.com/Pytxo-dev/pytxo](https://github.com/Pytxo-dev/pytxo) · Community:
|
|
58
|
+
[Discord](https://discord.gg/AUFRPFjSYv)
|
|
59
|
+
|
|
60
|
+
## How the package works
|
|
61
|
+
|
|
62
|
+
The first run downloads the same-version prebuilt binary from the public
|
|
63
|
+
[pytxo-releases](https://github.com/Pytxo-dev/pytxo-releases) repository and
|
|
64
|
+
checks it against the release's `SHA256SUMS.txt`. Package managers that run
|
|
65
|
+
install scripts do this during `postinstall` instead; npm 12 and pnpm 10 skip
|
|
66
|
+
it by default, and npm's warning about it is safe to ignore. Pytxo sends
|
|
67
|
+
nothing anywhere.
|
|
68
|
+
|
|
69
|
+
Set `PYTXO_SKIP_DOWNLOAD=1` and `PYTXO_BINARY=/path/to/pytxo` for local
|
|
70
|
+
development.
|
package/bin/pytxo.js
CHANGED
|
@@ -3,16 +3,28 @@
|
|
|
3
3
|
|
|
4
4
|
const { spawnSync } = require("node:child_process");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
6
7
|
|
|
7
8
|
const { dest, install } = require("../postinstall");
|
|
8
9
|
|
|
10
|
+
// Lets the CLI say "run npx pytxo again" when it was started that way.
|
|
11
|
+
const runAs = process.env.PYTXO_RUN_AS || (process.env.npm_command === "exec" ? "npx pytxo" : "pytxo");
|
|
12
|
+
|
|
9
13
|
function run(bin) {
|
|
10
|
-
|
|
14
|
+
// The binary handles Ctrl+C itself: it removes its scratch checkout, a
|
|
15
|
+
// second Ctrl+C quits it at once, and it exits 130. Waiting for it keeps the
|
|
16
|
+
// terminal from coming back while it cleans up.
|
|
17
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, () => {});
|
|
18
|
+
const result = spawnSync(bin, process.argv.slice(2), {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
env: { ...process.env, PYTXO_RUN_AS: runAs },
|
|
21
|
+
});
|
|
11
22
|
if (result.error) {
|
|
12
23
|
console.error(result.error.message);
|
|
13
24
|
process.exit(1);
|
|
14
25
|
}
|
|
15
|
-
|
|
26
|
+
// Killed by a signal: exit as a shell reports it, 128 plus its number.
|
|
27
|
+
process.exit(result.status ?? (result.signal ? 128 + os.constants.signals[result.signal] : 1));
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
async function main() {
|
package/lib/platform.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
function getAssetName(platform = process.platform, arch = process.arch) {
|
|
4
4
|
const assets = {
|
|
5
5
|
"win32:x64": "pytxo-windows-x64.exe",
|
|
6
|
+
// Windows on Arm runs x64 binaries under emulation.
|
|
7
|
+
"win32:arm64": "pytxo-windows-x64.exe",
|
|
6
8
|
"darwin:arm64": "pytxo-darwin-arm64",
|
|
7
9
|
"darwin:x64": "pytxo-darwin-x64",
|
|
8
10
|
"linux:arm64": "pytxo-linux-arm64",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pytxo",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Pytxo
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Land all your agents' branches in one command. Pytxo merges your Claude Code, Codex and Copilot branches in a throwaway worktree, runs your tests on the combined result, and lands what passes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
"bugs": "https://github.com/Pytxo-dev/pytxo-releases/issues",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"pytxo",
|
|
14
|
-
"
|
|
14
|
+
"claude-code",
|
|
15
|
+
"codex",
|
|
16
|
+
"copilot",
|
|
17
|
+
"cursor",
|
|
18
|
+
"coding-agents",
|
|
15
19
|
"ai-agents",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
+
"git",
|
|
21
|
+
"worktree",
|
|
22
|
+
"merge",
|
|
23
|
+
"ci",
|
|
24
|
+
"tests"
|
|
20
25
|
],
|
|
21
26
|
"engines": {
|
|
22
27
|
"node": ">=18"
|
package/postinstall.js
CHANGED
|
@@ -125,7 +125,10 @@ async function install() {
|
|
|
125
125
|
const checksumUrl = `${releaseBase}/SHA256SUMS.txt`;
|
|
126
126
|
const url = `${releaseBase}/${asset}`;
|
|
127
127
|
console.error(`pytxo: downloading ${asset} ${TAG} and checking it against SHA256SUMS.txt…`);
|
|
128
|
-
const
|
|
128
|
+
const fetched = (promise) => promise.catch((error) => {
|
|
129
|
+
throw Object.assign(error, { downloadFailed: true });
|
|
130
|
+
});
|
|
131
|
+
const checksumText = (await fetched(downloadBuffer(checksumUrl))).toString("utf8");
|
|
129
132
|
const checksum = expectedChecksum(checksumText, asset);
|
|
130
133
|
|
|
131
134
|
if (fs.existsSync(dest)) {
|
|
@@ -136,7 +139,7 @@ async function install() {
|
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
const bytes = await downloadBuffer(url);
|
|
142
|
+
const bytes = await fetched(downloadBuffer(url));
|
|
140
143
|
assertChecksum(bytes, checksum, asset);
|
|
141
144
|
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
142
145
|
const temp = path.join(
|
|
@@ -167,7 +170,11 @@ async function main() {
|
|
|
167
170
|
if (require.main === module) {
|
|
168
171
|
main().catch((error) => {
|
|
169
172
|
console.error(`pytxo: install failed: ${error.message}`);
|
|
170
|
-
|
|
173
|
+
// A download that did not happen (offline, a proxy) must not fail
|
|
174
|
+
// `npm install` or `npx`: the launcher tries again on first run. A binary
|
|
175
|
+
// that fails verification fails the install.
|
|
176
|
+
if (error.downloadFailed) console.error("pytxo: the first run of pytxo will try again.");
|
|
177
|
+
else process.exitCode = 1;
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
|