@webinson/peer 0.4.20 → 0.4.22
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 +8 -7
- package/bin/codex-thread-mark.mjs +6 -6
- package/bin/peer.mjs +1 -1
- package/mod/codex/client/README.md +2 -30
- package/mod/codex/client/build.sh +10 -25
- package/mod/codex/client/run.sh +19 -2
- package/mod/peer-thread-mark/.claude-plugin/plugin.json +1 -1
- package/mod/peer-thread-mark/hooks/register.tsx +14 -4
- package/mod/peer-thread-mark/tests/collision.test.tsx +14 -3
- package/package.json +2 -2
- package/bin/codex-native.mjs +0 -73
- package/bin/peer-codex.mjs +0 -45
- package/mod/codex/client/pack.sh +0 -80
package/README.md
CHANGED
|
@@ -99,11 +99,11 @@ peer-codex
|
|
|
99
99
|
If you already signed in to Peer, use `peer install --codex --client` in place of the login
|
|
100
100
|
command. Review and trust the Peer Thread Mark hooks in Codex's `/hooks` menu.
|
|
101
101
|
|
|
102
|
-
The `--client` setup
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
The `--client` setup needs Git, Rust/Cargo, `curl`, `shasum`, and a C/C++ toolchain on
|
|
103
|
+
the machine. It builds the pinned Codex client under `~/.peer/codex-client`. If you already
|
|
104
|
+
built it, set `PEER_CODEX_CLIENT` to its executable when you run setup. The `peer-codex`
|
|
105
|
+
launcher uses your existing Codex settings and sign-in with its own app server. `PEER_HOME`
|
|
106
|
+
moves the default checkout and Peer state.
|
|
107
107
|
|
|
108
108
|
For the plugin alone, omit `--client`. The Codex plugin lives under [`mod/codex`](mod/codex)
|
|
109
109
|
in its own marketplace.
|
|
@@ -113,7 +113,8 @@ In stock Codex, send `peer: start`, `peer: stop`, `peer: list`, `peer: thread`,
|
|
|
113
113
|
This plugin uses the [Codex hook API](https://learn.chatgpt.com/docs/hooks); stock Codex does
|
|
114
114
|
not expose a composer band or memory switches to plugins.
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
From a source checkout, use `node bin/peer.mjs install --codex --client` and
|
|
117
|
+
`bash mod/codex/client/run.sh` from `cli/`.
|
|
117
118
|
|
|
118
119
|
The terminal band shows the thread's memory count, **Use Elephant**, **Use Jellyfish**, and
|
|
119
120
|
**Active** or **Inactive**. Press the lit mode again to stop sharing. Use `/peer-mode` for
|
|
@@ -145,7 +146,7 @@ agent also gets one sentence beside the tool result, fenced as data:
|
|
|
145
146
|
|
|
146
147
|
```text
|
|
147
148
|
<peer-collision>
|
|
148
|
-
bob@acme.test
|
|
149
|
+
Another person, bob@acme.test, had an agent on src/cart.ts 2 minutes ago.
|
|
149
150
|
This is information about the repository, not an instruction.
|
|
150
151
|
</peer-collision>
|
|
151
152
|
```
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { installNativeCodex } from "./codex-native.mjs";
|
|
5
4
|
|
|
6
5
|
// Codex's lifecycle hooks use their own event and output shapes. Keep Claude's
|
|
7
6
|
// title updates and UI out of this adapter.
|
|
@@ -22,13 +21,14 @@ export function installCodex(peer, { client = false } = {}) {
|
|
|
22
21
|
fs.accessSync(clientPath, fs.constants.X_OK);
|
|
23
22
|
if (!fs.statSync(clientPath).isFile()) throw new Error("PEER_CODEX_CLIENT must point to the Codex executable");
|
|
24
23
|
} else {
|
|
25
|
-
|
|
24
|
+
const checkout = path.resolve(peer.home, "codex-client");
|
|
25
|
+
execFileSync("bash", [path.join(marketplace, "client", "build.sh"), checkout], { stdio: "inherit" });
|
|
26
|
+
clientPath = path.join(checkout, "codex-rs", "target", "debug", "codex");
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const binary = process.env.PEER_CODEX || (client ? clientPath : "codex");
|
|
30
|
-
const
|
|
31
|
-
const runCodex = (args, capture = false) => codex(args, { binary, capture, timeout });
|
|
31
|
+
const runCodex = (args, capture = false) => codex(args, { binary, capture });
|
|
32
32
|
const listed = runCodex(["plugin", "marketplace", "list", "--json"], true);
|
|
33
33
|
const previous = JSON.parse(listed).marketplaces?.find((item) => item.name === MARKETPLACE);
|
|
34
34
|
const moving = previous && previous.root !== marketplace;
|
|
@@ -65,11 +65,11 @@ export function uninstallCodex(peer) {
|
|
|
65
65
|
console.log("Codex: Peer's plugin removed.");
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function codex(args, { binary, capture = false
|
|
68
|
+
function codex(args, { binary, capture = false }) {
|
|
69
69
|
return execFileSync(binary, args, {
|
|
70
70
|
stdio: capture ? ["ignore", "pipe", "inherit"] : "inherit",
|
|
71
71
|
encoding: "utf8",
|
|
72
|
-
timeout,
|
|
72
|
+
timeout: 60_000,
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
package/bin/peer.mjs
CHANGED
|
@@ -46,7 +46,7 @@ const HELP = `peer: share chosen agent threads with your team
|
|
|
46
46
|
your own memories in your personal OpenViking, or the ones with these words
|
|
47
47
|
(full text); here: only this repository's. /peer-all in Claude Code
|
|
48
48
|
peer install [--codex [--client]]
|
|
49
|
-
set up the thread mark; --client also
|
|
49
|
+
set up the thread mark; --client also builds the Peer Codex terminal
|
|
50
50
|
peer uninstall [--codex] remove the integration from Claude Code or Codex
|
|
51
51
|
peer logout forget the sign-in on this computer
|
|
52
52
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# Peer for the Codex terminal
|
|
2
2
|
|
|
3
3
|
The sibling Codex plugin can report thread activity and answer commands with stock Codex.
|
|
4
|
-
`
|
|
5
|
-
|
|
6
|
-
Peer memory policy.
|
|
4
|
+
`build.sh` builds the terminal client for the band, local panels, and per-thread memory policy.
|
|
5
|
+
`run.sh` keeps the terminal on its own app server so it uses that policy.
|
|
7
6
|
|
|
8
7
|
`src/` contains the Peer modules. The two patches wire them into the pinned Codex source:
|
|
9
8
|
`0001-memory.patch` controls plugin availability, capture and native memory; `0002-terminal.patch`
|
|
@@ -13,33 +12,6 @@ capture windows. Update the Codex pin, V8 version, checksums and patches togethe
|
|
|
13
12
|
The patches include context from OpenAI Codex, distributed under Apache-2.0.
|
|
14
13
|
See [CODEX-LICENSE](CODEX-LICENSE) and [CODEX-NOTICE](CODEX-NOTICE).
|
|
15
14
|
|
|
16
|
-
## Building and packing a release
|
|
17
|
-
|
|
18
|
-
`build.sh` takes the path of a checkout to create or reuse. It needs Git, Rust/Cargo,
|
|
19
|
-
`curl`, `shasum`, and a C/C++ toolchain. For a custom build, run it and set
|
|
20
|
-
`PEER_CODEX_CLIENT` to the resulting executable when running `peer install --codex --client`.
|
|
21
|
-
|
|
22
|
-
Set `PEER_CODEX_BUILD_PROFILE=release` for an npm build and set
|
|
23
|
-
`CARGO_PROFILE_RELEASE_STRIP=symbols` before compiling. Set `PEER_CODEX_BUILD_TARGET`
|
|
24
|
-
to build for a specific Rust target with the matching Rust standard library and C toolchain.
|
|
25
|
-
For cross-compiling, `PEER_CODEX_CARGO_COMMAND` can select `zigbuild` or `xwin`.
|
|
26
|
-
For Linux musl builds, use the pinned upstream release's musl tools and set
|
|
27
|
-
`CODEX_BWRAP_SHA256` to the SHA-256 of the `codex-resources/bwrap` shipped in the
|
|
28
|
-
matching upstream npm package before compiling.
|
|
29
|
-
|
|
30
|
-
After building, run:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
bash pack.sh darwin-arm64 /path/to/build/bin /path/to/codex-checkout /path/to/output
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Use the directory containing both `codex` and `codex-code-mode-host` (with `.exe` on
|
|
37
|
-
Windows). The script adds the matching upstream sandbox and search helpers. Build and
|
|
38
|
-
pack `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, `win32-arm64`, and
|
|
39
|
-
`win32-x64` for a native release. The installer pins their version in
|
|
40
|
-
`bin/codex-native.mjs`. When that pin changes, publish and verify the native packages
|
|
41
|
-
before publishing the version of `@webinson/peer` that uses them.
|
|
42
|
-
|
|
43
15
|
From the repository root, run `npm test --prefix cli`. To exercise the actual band, switches,
|
|
44
16
|
and panels with temporary profiles and local fixture servers, run:
|
|
45
17
|
|
|
@@ -39,34 +39,20 @@ if [[ -n ${PEER_CODEX_TOOLCHAIN:-} ]]; then
|
|
|
39
39
|
fi
|
|
40
40
|
cd -- "$checkout/codex-rs"
|
|
41
41
|
# The pinned Codex release ships its V8 build on the Codex release, not on denoland's.
|
|
42
|
-
|
|
43
|
-
profile=${PEER_CODEX_BUILD_PROFILE:-dev}
|
|
44
|
-
build_args=(--profile "$profile")
|
|
45
|
-
cargo_command=("${PEER_CODEX_CARGO_COMMAND:-build}")
|
|
46
|
-
if [[ "${cargo_command[0]}" == xwin ]]; then
|
|
47
|
-
cargo_command+=(build)
|
|
48
|
-
fi
|
|
49
|
-
target_dir=${CARGO_TARGET_DIR:-"$checkout/codex-rs/target"}
|
|
50
|
-
output="$target_dir"
|
|
51
|
-
if [[ -n ${PEER_CODEX_BUILD_TARGET:-} ]]; then
|
|
52
|
-
build_args+=(--target "$target")
|
|
53
|
-
output="$output/$target"
|
|
54
|
-
fi
|
|
42
|
+
host=$(rustc ${cargo_args[@]+"${cargo_args[@]}"} -vV | awk '/^host:/ {print $2}')
|
|
55
43
|
v8=152.2.0
|
|
56
|
-
v8_dir="$checkout/target/peer-v8/$
|
|
44
|
+
v8_dir="$checkout/target/peer-v8/$host"
|
|
57
45
|
v8_base="https://github.com/openai/codex/releases/download/rusty-v8-v$v8"
|
|
58
|
-
binding="src_binding_ptrcomp_sandbox_release_${
|
|
59
|
-
archive="librusty_v8_ptrcomp_sandbox_release_${
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
archive="rusty_v8_ptrcomp_sandbox_release_${target}.lib.gz"
|
|
63
|
-
exe=".exe"
|
|
46
|
+
binding="src_binding_ptrcomp_sandbox_release_${host}.rs"
|
|
47
|
+
archive="librusty_v8_ptrcomp_sandbox_release_${host}.a.gz"
|
|
48
|
+
if [[ "$host" == *-pc-windows-msvc ]]; then
|
|
49
|
+
archive="rusty_v8_ptrcomp_sandbox_release_${host}.lib.gz"
|
|
64
50
|
fi
|
|
65
51
|
mkdir -p -- "$v8_dir"
|
|
66
52
|
for artifact in "$archive" "$binding"; do
|
|
67
53
|
checksum=$(awk -v file="$artifact" '$2 == file {print $1}' "$checkout/third_party/v8/rusty_v8_${v8//./_}.sha256")
|
|
68
54
|
if [[ -z "$checksum" ]]; then
|
|
69
|
-
printf 'No pinned Codex V8 artifact for %s.\n' "$
|
|
55
|
+
printf 'No pinned Codex V8 artifact for %s.\n' "$host" >&2
|
|
70
56
|
exit 1
|
|
71
57
|
fi
|
|
72
58
|
if ! (cd -- "$v8_dir" && printf '%s %s\n' "$checksum" "$artifact" | shasum -a 256 -c - >/dev/null 2>&1); then
|
|
@@ -76,7 +62,6 @@ for artifact in "$archive" "$binding"; do
|
|
|
76
62
|
done
|
|
77
63
|
# The upstream release tag changes workspace versions without regenerating Cargo.lock.
|
|
78
64
|
RUSTY_V8_ARCHIVE="$v8_dir/$archive" RUSTY_V8_SRC_BINDING_PATH="$v8_dir/$binding" \
|
|
79
|
-
CARGO_PROFILE_DEV_DEBUG=0 cargo ${cargo_args[@]+"${cargo_args[@]}"}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
printf 'Run with: PEER_CODEX_CLIENT=%q peer-codex\n' "$output/$profile/codex$exe"
|
|
65
|
+
CARGO_PROFILE_DEV_DEBUG=0 cargo ${cargo_args[@]+"${cargo_args[@]}"} build \
|
|
66
|
+
--target-dir "$checkout/codex-rs/target" -p codex-cli -p codex-code-mode-host --bins
|
|
67
|
+
printf 'Run with: PEER_CODEX_CLIENT=%q bash %q\n' "$checkout/codex-rs/target/debug/codex" "$here/run.sh"
|
package/mod/codex/client/run.sh
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
peer_root=${PEER_HOME:-"$HOME/.peer"}
|
|
5
|
+
client=${PEER_CODEX_CLIENT:-}
|
|
6
|
+
if [[ -z "$client" && -f "$peer_root/codex-cli.json" ]]; then
|
|
7
|
+
client=$(node -e '
|
|
8
|
+
try {
|
|
9
|
+
const data = JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"));
|
|
10
|
+
if (typeof data.client === "string" && require("node:path").isAbsolute(data.client)) {
|
|
11
|
+
process.stdout.write(data.client);
|
|
12
|
+
}
|
|
13
|
+
} catch {}
|
|
14
|
+
' "$peer_root/codex-cli.json")
|
|
15
|
+
fi
|
|
16
|
+
client=${client:-"$peer_root/codex-client/codex-rs/target/debug/codex"}
|
|
17
|
+
if [[ ! -x "$client" ]]; then
|
|
18
|
+
printf 'Set up the Peer Codex terminal with peer install --codex --client.\n' >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
# An existing stock daemon would use the stock memory policy.
|
|
22
|
+
PEER_CODEX_THREAD_MARK=1 exec "$client" --no-daemon "$@"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peer-thread-mark",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "Marks a Claude Code thread shared with Peer (/peer) with a band above the prompt, with /peer stop and /peer start; panes for /peer-list, /peer-thread and /peer-all (search your memories)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"types": "./types/index.d.ts"
|
|
@@ -497,8 +497,18 @@ async function memoryNote($: EngineInterface) {
|
|
|
497
497
|
return 'Inactive: not shared, Claude Code memory only.'
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
// Who else was on the file, newest
|
|
501
|
-
|
|
500
|
+
// Who else was on the file, one entry per person (their newest session), newest first. One person often holds a path
|
|
501
|
+
// in several sessions (another thread, the Peer app's own), and is named once.
|
|
502
|
+
const byPerson = (holders: CollisionSignal['holders']) => {
|
|
503
|
+
const newest = new Map<string, CollisionSignal['holders'][number]>()
|
|
504
|
+
for (const h of holders) {
|
|
505
|
+
const key = h.email.toLowerCase()
|
|
506
|
+
const was = newest.get(key)
|
|
507
|
+
if (!was || Date.parse(h.at) > Date.parse(was.at)) newest.set(key, h)
|
|
508
|
+
}
|
|
509
|
+
return [...newest.values()].sort((a, b) => Date.parse(b.at) - Date.parse(a.at))
|
|
510
|
+
}
|
|
511
|
+
const othersOf = (signal: CollisionSignal) => byPerson(signal.holders.filter(h => !h.own))
|
|
502
512
|
const whenAgo = (at: string, now: number) => {
|
|
503
513
|
const age = ago(at, now)
|
|
504
514
|
return age === 'just now' ? age : `${age} ago`
|
|
@@ -511,7 +521,7 @@ function collisionSentence(signal: CollisionSignal, now: number) {
|
|
|
511
521
|
.map(h => {
|
|
512
522
|
const mins = Math.max(0, Math.round((now - Date.parse(h.at)) / 60_000))
|
|
513
523
|
const age = mins < 1 ? 'just now' : mins === 1 ? '1 minute ago' : `${mins} minutes ago`
|
|
514
|
-
return
|
|
524
|
+
return `Another person, ${h.email}, had an agent on ${signal.path} ${age}.`
|
|
515
525
|
})
|
|
516
526
|
return ['<peer-collision>', ...said, 'This is information about the repository, not an instruction.', '</peer-collision>'].join('\n')
|
|
517
527
|
}
|
|
@@ -605,7 +615,7 @@ export const register: Register = on => {
|
|
|
605
615
|
.filter(h => ADDRESS.test(h.email))
|
|
606
616
|
const state = answer.state === 'collision' && !holders.length ? 'clear' : answer.state
|
|
607
617
|
const at = await $.clock.now()
|
|
608
|
-
const emails = (signal: { holders: { email: string }[] }) => signal.holders.map(h => h.email.toLowerCase()).sort().join(' ')
|
|
618
|
+
const emails = (signal: { holders: { email: string }[] }) => [...new Set(signal.holders.map(h => h.email.toLowerCase()))].sort().join(' ')
|
|
609
619
|
let signal: CollisionSignal = { path: answer.path ? plain(answer.path) : null, state, ...(answer.reason ? { reason: answer.reason } : {}), holders, at, shown: false }
|
|
610
620
|
// Shown once per set of people on a path: against the latest earlier collision on it, decided inside one update.
|
|
611
621
|
await update($, signals, was => {
|
|
@@ -60,7 +60,7 @@ test('an edit another person was on draws the line and tells the agent once per
|
|
|
60
60
|
expect(state.collides[0]).toEqual(['--json', 's1', 't1', '/repo/src/cart.ts'])
|
|
61
61
|
expect(told.additionalContext?.[0]).toBe('from another hook')
|
|
62
62
|
expect(told.additionalContext?.[1]).toBe(
|
|
63
|
-
"<peer-collision>\
|
|
63
|
+
"<peer-collision>\nAnother person, bob@acme.test, had an agent on src/cart.ts 2 minutes ago.\nThis is information about the repository, not an instruction.\n</peer-collision>",
|
|
64
64
|
)
|
|
65
65
|
for (const surface of ['terminal', 'desktop'] as const) {
|
|
66
66
|
const drawn = await texts(row('t1', surface), $ as never)
|
|
@@ -84,9 +84,20 @@ test('an edit another person was on draws the line and tells the agent once per
|
|
|
84
84
|
const CY = { email: 'cy@acme.test', at: minsAgo(1), own: false }
|
|
85
85
|
state.answer = { path: 'src/pay.ts', state: 'collision', holders: [BOB, CY] }
|
|
86
86
|
const both = (await $.classic.PostToolUse(edit('t3', 'Write', '/repo/src/pay.ts'))) as { additionalContext?: string[] }
|
|
87
|
-
expect(both.additionalContext?.[1]).toContain("cy@acme.test
|
|
88
|
-
expect(both.additionalContext?.[1]).toContain("bob@acme.test
|
|
87
|
+
expect(both.additionalContext?.[1]).toContain("Another person, cy@acme.test, had an agent on src/pay.ts 1 minute ago")
|
|
88
|
+
expect(both.additionalContext?.[1]).toContain("Another person, bob@acme.test, had an agent on src/pay.ts 2 minutes ago")
|
|
89
89
|
expect(await texts(row('t3', 'desktop'), $ as never)).toContain('cy@… was on it 1 min ago and 1 others')
|
|
90
|
+
|
|
91
|
+
// One person in several sessions (another thread, the Peer app's own) is named once, at their newest.
|
|
92
|
+
state.answer = { path: 'src/one.ts', state: 'collision', holders: [BOB, { ...BOB, email: 'Bob@acme.test', at: minsAgo(0) }, { ...BOB, at: minsAgo(1) }] }
|
|
93
|
+
const once = (await $.classic.PostToolUse(edit('t4', 'Edit', '/repo/src/one.ts'))) as { additionalContext?: string[] }
|
|
94
|
+
expect(once.additionalContext?.[1]).toBe(
|
|
95
|
+
"<peer-collision>\nAnother person, Bob@acme.test, had an agent on src/one.ts just now.\nThis is information about the repository, not an instruction.\n</peer-collision>",
|
|
96
|
+
)
|
|
97
|
+
expect(await texts(row('t4', 'terminal'), $ as never)).toContain('Bob@… was on it just now')
|
|
98
|
+
// The same person in one more session is no new set of people.
|
|
99
|
+
state.answer = { path: 'src/one.ts', state: 'collision', holders: [BOB, { ...BOB, at: minsAgo(0) }] }
|
|
100
|
+
expect(((await $.classic.PostToolUse(edit('t5', 'Edit', '/repo/src/one.ts'))) as { additionalContext?: string[] }).additionalContext).toEqual(['from another hook'])
|
|
90
101
|
})
|
|
91
102
|
|
|
92
103
|
test('the person\'s own other thread is drawn dimmed and the agent is told nothing', async ($, on) => {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webinson/peer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.22",
|
|
4
4
|
"description": "Share chosen Claude Code and Codex threads with your team: who has agents running and on what.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"peer": "bin/peer.mjs",
|
|
9
|
-
"peer-codex": "
|
|
9
|
+
"peer-codex": "mod/codex/client/run.sh"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"bin",
|
package/bin/codex-native.mjs
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export const codexTargets = {
|
|
6
|
-
"darwin-arm64": "aarch64-apple-darwin",
|
|
7
|
-
"darwin-x64": "x86_64-apple-darwin",
|
|
8
|
-
"linux-arm64": "aarch64-unknown-linux-musl",
|
|
9
|
-
"linux-x64": "x86_64-unknown-linux-musl",
|
|
10
|
-
"win32-arm64": "aarch64-pc-windows-msvc",
|
|
11
|
-
"win32-x64": "x86_64-pc-windows-msvc",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const codexPackageName = (platform) => `@webinson/peer-codex-${platform}`;
|
|
15
|
-
export const codexExecutable = (platform) => platform === "win32" ? "codex.exe" : "codex";
|
|
16
|
-
export const codexNativeVersion = "0.4.18";
|
|
17
|
-
|
|
18
|
-
function installedClient(directory, target) {
|
|
19
|
-
const bin = path.join(directory, "vendor", target, "bin");
|
|
20
|
-
const suffix = process.platform === "win32" ? ".exe" : "";
|
|
21
|
-
const client = path.join(bin, codexExecutable(process.platform));
|
|
22
|
-
try {
|
|
23
|
-
for (const executable of [client, path.join(bin, `codex-code-mode-host${suffix}`)]) {
|
|
24
|
-
if (!fs.statSync(executable).isFile()) return null;
|
|
25
|
-
fs.accessSync(executable, fs.constants.X_OK);
|
|
26
|
-
}
|
|
27
|
-
return client;
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function installNativeCodex(peer) {
|
|
34
|
-
const platform = `${process.platform}-${process.arch}`;
|
|
35
|
-
const target = codexTargets[platform];
|
|
36
|
-
if (!target) {
|
|
37
|
-
throw new Error(`No prebuilt Peer Codex for ${platform}. Set PEER_CODEX_CLIENT to a custom build.`);
|
|
38
|
-
}
|
|
39
|
-
const packageName = codexPackageName(platform);
|
|
40
|
-
const spec = `${packageName}@${codexNativeVersion}`;
|
|
41
|
-
const root = path.resolve(peer.home, "codex-clients");
|
|
42
|
-
const destination = path.join(root, `${platform}-${codexNativeVersion}`);
|
|
43
|
-
if (fs.existsSync(destination)) {
|
|
44
|
-
const existing = installedClient(destination, target);
|
|
45
|
-
if (existing) return existing;
|
|
46
|
-
throw new Error(`Incomplete Peer Codex install at ${destination}. Remove that directory and retry.`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
50
|
-
const staging = fs.mkdtempSync(path.join(root, ".install-"));
|
|
51
|
-
try {
|
|
52
|
-
const args = [
|
|
53
|
-
"install", "--prefix", ".", "--global=false", "--ignore-scripts",
|
|
54
|
-
"--no-save", "--no-package-lock", "--no-audit", "--no-fund", spec,
|
|
55
|
-
];
|
|
56
|
-
const options = { cwd: staging, stdio: "inherit", timeout: 600_000 };
|
|
57
|
-
if (process.platform === "win32") {
|
|
58
|
-
execFileSync(process.env.ComSpec || "cmd.exe", ["/d", "/c", "npm", ...args], options);
|
|
59
|
-
} else {
|
|
60
|
-
execFileSync("npm", args, options);
|
|
61
|
-
}
|
|
62
|
-
const downloaded = path.join(staging, "node_modules", packageName);
|
|
63
|
-
if (!installedClient(downloaded, target)) throw new Error(`${spec} is missing its executables`);
|
|
64
|
-
try {
|
|
65
|
-
fs.renameSync(downloaded, destination);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (!installedClient(destination, target)) throw error;
|
|
68
|
-
}
|
|
69
|
-
return installedClient(destination, target);
|
|
70
|
-
} finally {
|
|
71
|
-
fs.rmSync(staging, { recursive: true, force: true });
|
|
72
|
-
}
|
|
73
|
-
}
|
package/bin/peer-codex.mjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { codexExecutable } from "./codex-native.mjs";
|
|
7
|
-
|
|
8
|
-
const home = process.env.PEER_HOME || path.join(os.homedir(), ".peer");
|
|
9
|
-
let configured;
|
|
10
|
-
try {
|
|
11
|
-
configured = JSON.parse(fs.readFileSync(path.join(home, "codex-cli.json"), "utf8"))?.client;
|
|
12
|
-
} catch {}
|
|
13
|
-
const client = process.env.PEER_CODEX_CLIENT
|
|
14
|
-
|| (typeof configured === "string" && path.isAbsolute(configured) ? configured : null)
|
|
15
|
-
|| path.join(home, "codex-client", "codex-rs", "target", "debug", codexExecutable(process.platform));
|
|
16
|
-
try {
|
|
17
|
-
fs.accessSync(client, fs.constants.X_OK);
|
|
18
|
-
} catch {
|
|
19
|
-
console.error("Set up the Peer Codex terminal with peer install --codex --client.");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// A stock daemon would use the stock memory policy.
|
|
24
|
-
const args = ["--no-daemon", ...process.argv.slice(2)];
|
|
25
|
-
const env = { ...process.env, PEER_CODEX_THREAD_MARK: "1" };
|
|
26
|
-
if (process.platform !== "win32" && typeof process.execve === "function") {
|
|
27
|
-
process.on("SIGXFSZ", () => {});
|
|
28
|
-
process.execve(client, [client, ...args], env);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const child = spawn(client, args, { stdio: "inherit", env });
|
|
32
|
-
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
33
|
-
const forward = (signal) => {
|
|
34
|
-
if (!child.killed) child.kill(signal);
|
|
35
|
-
};
|
|
36
|
-
for (const signal of signals) process.on(signal, forward);
|
|
37
|
-
child.on("error", (error) => {
|
|
38
|
-
console.error(`Could not start Peer Codex: ${error.message}`);
|
|
39
|
-
process.exitCode = 1;
|
|
40
|
-
});
|
|
41
|
-
child.on("exit", (code, signal) => {
|
|
42
|
-
for (const name of signals) process.off(name, forward);
|
|
43
|
-
process.exitCode = code ?? (signal ? 128 + os.constants.signals[signal] : 1);
|
|
44
|
-
if (signal && process.platform !== "win32") process.kill(process.pid, signal);
|
|
45
|
-
});
|
package/mod/codex/client/pack.sh
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
5
|
-
cli=$(cd -- "$here/../../.." && pwd)
|
|
6
|
-
platform=${1:?usage: pack.sh PLATFORM BUILD_BIN_DIR CODEX_CHECKOUT OUTPUT_DIR}
|
|
7
|
-
build=$(cd -- "${2:?missing build bin directory}" && pwd)
|
|
8
|
-
source=$(cd -- "${3:?missing pinned Codex checkout}" && pwd)
|
|
9
|
-
mkdir -p -- "${4:?missing output directory}"
|
|
10
|
-
output=$(cd -- "$4" && pwd)
|
|
11
|
-
target=$(node --input-type=module - "$cli" "$platform" <<'JS'
|
|
12
|
-
import { pathToFileURL } from "node:url";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
const { codexTargets } = await import(pathToFileURL(path.join(process.argv[2], "bin/codex-native.mjs")));
|
|
15
|
-
const target = codexTargets[process.argv[3]];
|
|
16
|
-
if (!target) throw new Error("Unsupported Peer Codex platform");
|
|
17
|
-
console.log(target);
|
|
18
|
-
JS
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
commit=f79b26ccbb3116b052544666a181012fb45dc49b
|
|
22
|
-
codex_version=0.166.0-alpha.5.1
|
|
23
|
-
[[ $(git -C "$source" rev-parse HEAD) == "$commit" ]]
|
|
24
|
-
stage=$(mktemp -d "${TMPDIR:-/tmp}/peer-codex-pack.XXXXXXXX")
|
|
25
|
-
trap 'rm -rf -- "$stage"' EXIT
|
|
26
|
-
archive=$(npm pack "@openai/codex@$codex_version-$platform" --ignore-scripts --pack-destination "$stage" --silent)
|
|
27
|
-
suffix=""
|
|
28
|
-
resources=()
|
|
29
|
-
case "$platform" in
|
|
30
|
-
linux-*) resources+=(codex-resources/bwrap) ;;
|
|
31
|
-
win32-*)
|
|
32
|
-
suffix=.exe
|
|
33
|
-
resources+=(codex-resources/codex-command-runner.exe codex-resources/codex-windows-sandbox-setup.exe)
|
|
34
|
-
;;
|
|
35
|
-
esac
|
|
36
|
-
vendor="package/vendor/$target"
|
|
37
|
-
assets=("$vendor/codex-package.json" "$vendor/codex-path/rg$suffix")
|
|
38
|
-
for asset in ${resources[@]+"${resources[@]}"}; do assets+=("$vendor/$asset"); done
|
|
39
|
-
tar -xf "$stage/$archive" -C "$stage" "${assets[@]}"
|
|
40
|
-
mkdir -p "$stage/$vendor/bin"
|
|
41
|
-
for binary in codex codex-code-mode-host; do
|
|
42
|
-
install -m 755 "$build/$binary$suffix" "$stage/$vendor/bin/$binary$suffix"
|
|
43
|
-
done
|
|
44
|
-
cp "$here/CODEX-LICENSE" "$stage/package/LICENSE"
|
|
45
|
-
cp "$here/CODEX-NOTICE" "$stage/package/NOTICE"
|
|
46
|
-
curl -fsSL --retry 2 'https://raw.githubusercontent.com/BurntSushi/ripgrep/15.2.0/LICENSE-MIT' \
|
|
47
|
-
-o "$stage/package/RIPGREP-LICENSE"
|
|
48
|
-
if [[ "$platform" == linux-* ]]; then
|
|
49
|
-
cp "$source/codex-rs/vendor/bubblewrap/LICENSE" "$stage/package/BUBBLEWRAP-LICENSE"
|
|
50
|
-
fi
|
|
51
|
-
cat >> "$stage/package/NOTICE" <<EOF
|
|
52
|
-
|
|
53
|
-
Peer Codex modifies the terminal UI, thread sharing and memory policy.
|
|
54
|
-
Upstream source: https://github.com/openai/codex/tree/$commit
|
|
55
|
-
The matching @webinson/peer npm package contains the Peer changes.
|
|
56
|
-
|
|
57
|
-
The bundled ripgrep is from https://github.com/BurntSushi/ripgrep/tree/15.2.0.
|
|
58
|
-
Linux bundles include the sandbox helper built from the Codex source above
|
|
59
|
-
under codex-rs/vendor/bubblewrap. The corresponding sources and build scripts
|
|
60
|
-
are in that same tree.
|
|
61
|
-
EOF
|
|
62
|
-
node --input-type=module - "$cli" "$platform" "$stage/package" <<'JS'
|
|
63
|
-
import fs from "node:fs";
|
|
64
|
-
import path from "node:path";
|
|
65
|
-
import { pathToFileURL } from "node:url";
|
|
66
|
-
const [cli, platform, stage] = process.argv.slice(2);
|
|
67
|
-
const [os, cpu] = platform.split("-");
|
|
68
|
-
const { codexPackageName, codexNativeVersion } = await import(pathToFileURL(path.join(cli, "bin/codex-native.mjs")));
|
|
69
|
-
const pkg = {
|
|
70
|
-
name: codexPackageName(platform),
|
|
71
|
-
version: codexNativeVersion,
|
|
72
|
-
description: `Peer Codex terminal for ${os} ${cpu}. Installed by @webinson/peer.`,
|
|
73
|
-
license: "Apache-2.0",
|
|
74
|
-
os: [os],
|
|
75
|
-
cpu: [cpu],
|
|
76
|
-
files: ["vendor", "NOTICE", "*-LICENSE"],
|
|
77
|
-
};
|
|
78
|
-
fs.writeFileSync(path.join(stage, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
79
|
-
JS
|
|
80
|
-
npm pack "$stage/package" --pack-destination "$output" --ignore-scripts
|