relay-codex 0.0.0-stage → 0.5.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 blackspider-ops
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,119 @@
1
- # Temporary Holding Version
1
+ <p align="center"><img src="https://raw.githubusercontent.com/blackspider-ops/relay/main/assets/relay.svg" alt="Relay — keep Codex moving" width="840"></p>
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Relay keeps your Codex sessions moving between your own accounts. When an account
4
+ runs out, it finds another available account. When all accounts are waiting for a
5
+ reset, it waits too, then continues the saved task.
6
+
7
+ **macOS · Native Codex terminal · One account or many**
8
+
9
+ ## Get started
10
+
11
+ You'll need [Codex](https://learn.chatgpt.com/docs/cli), Node.js 20 or newer, and
12
+ Python 3.11 or newer. If you use Homebrew, `brew install node python@3.13` installs
13
+ Node and Python.
14
+
15
+ ```sh
16
+ npm install -g relay-codex
17
+ relay setup --count 2
18
+ relay run
19
+ ```
20
+
21
+ Choose however many accounts you have. Each person signs into their own accounts;
22
+ this project does not include anyone's saved logins. Existing logins from the
23
+ earlier `codex-accounts` tool are picked up automatically.
24
+
25
+ The first run downloads Relay's Python libraries. Later runs use the saved copy.
26
+ Your usual `codex` command keeps working.
27
+
28
+ Prefer trying it without a global install?
29
+
30
+ ```sh
31
+ npx --yes relay-codex setup --count 2
32
+ npx --yes relay-codex run
33
+ ```
34
+
35
+ ## Use it like Codex
36
+
37
+ ```sh
38
+ relay run -- resume --yolo
39
+ relay run -- -C ~/projects/website resume
40
+ relay run -- exec --json "Review this project"
41
+ relay status --watch
42
+ relay menubar
43
+ ```
44
+
45
+ `--yolo` keeps its usual full-access meaning. Without it, Relay keeps the task's
46
+ permissions. Resume starts with sessions from your current folder, just like
47
+ `codex resume`. Add `--all` to browse every folder, or `-C PATH` to choose a folder.
48
+ Different terminals can work on different conversations at once.
49
+
50
+ Put native Codex commands and options after `relay run --`. Use `relay run --help`
51
+ for the full Codex command and flag list, or `relay help` for Relay management.
52
+ Profiles, extra writable directories, Git worktrees, and noninteractive tasks can
53
+ use the account pool. `relay explain -- <arguments>` shows how a launch will run.
54
+ Native utilities, external/local-provider endpoints, and a few native-only setup
55
+ flags run directly; `--require-recovery` rejects a launch outside Relay recovery.
56
+
57
+ If a conversation is already open in another app, close it there before resuming
58
+ it here, or use Codex's `fork` command for a separate copy. To move a Relay task
59
+ between terminals:
60
+
61
+ ```sh
62
+ relay attach --takeover SESSION_ID
63
+ ```
64
+
65
+ Accounts show their saved email addresses. The watch view updates only when local
66
+ status changes; it uses no model allowance. The menu bar uses an icon and opens
67
+ at Mac login. `relay menubar --no-startup --configure-only` disables login startup.
68
+
69
+ ## What happens when something stops?
70
+
71
+ - **Account limit:** use an available account, or wait for the earliest usable reset.
72
+ - **One account:** wait for its reset and continue the same conversation.
73
+ - **Connection or sign-in failure:** retry at 5, 10, and 15 minutes, then try another account.
74
+ - **“Selected model is at capacity”:** wait five minutes and try the same model again.
75
+ Relay keeps waiting rather than cycling logins. A different model is used only
76
+ if you explicitly configure a fallback.
77
+ - **An unclear interruption during a command:** pause for review before continuing.
78
+ Relay cannot guarantee that an external action happened exactly once.
79
+
80
+ ## Leave work running
81
+
82
+ ```sh
83
+ relay start --name website --cwd ~/projects/website --prompt "Finish the remaining checks"
84
+ relay attach SESSION_ID
85
+ relay stop SESSION_ID
86
+ ```
87
+
88
+ Background tasks keep running after their terminal closes. Use `relay status --all`
89
+ to find saved sessions and `relay restart SESSION_ID` to recover a stopped one.
90
+ Opt into task recovery at Mac login with `relay startup enable SESSION_ID`.
91
+ `relay workspace save daily` and `relay workspace restore daily` retain the same
92
+ conversations across several terminals.
93
+
94
+ ## A few useful commands
95
+
96
+ ```sh
97
+ relay add personal
98
+ relay account disable personal
99
+ relay pause SESSION_ID
100
+ relay unpause SESSION_ID
101
+ relay retry SESSION_ID
102
+ relay doctor --self-test
103
+ relay menubar
104
+ ```
105
+
106
+ Accounts stay in your Mac's Keychain. Relay keeps its settings and task records
107
+ in your private Application Support folder. Never share that folder.
108
+
109
+ ## Updates and limits
110
+
111
+ Relay checks the installed Codex version and keeps a working copy for running
112
+ sessions. Future Codex changes may still need a Relay update.
113
+ Update Relay with `npm install -g relay-codex@latest`.
114
+
115
+ This is an independent tool, not an OpenAI product. Short local tests cover
116
+ parallel tasks and recovery; they are not an overnight reliability guarantee.
117
+
118
+ [Commands](docs/commands.md) · [Troubleshooting](docs/troubleshooting.md) ·
119
+ [Publishing to npm or Homebrew](docs/publishing.md) · [Contributing](CONTRIBUTING.md)
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="360" viewBox="0 0 1280 360" role="img" aria-labelledby="title desc">
2
+ <title id="title">Relay</title>
3
+ <desc id="desc">Keep Codex moving. Four paths meet in one forward arrow.</desc>
4
+ <rect width="1280" height="360" rx="24" fill="#111916"/>
5
+ <g fill="none" stroke="#a8d9bd" stroke-width="10" stroke-linecap="round">
6
+ <path d="M96 108h62c48 0 44 72 94 72h44" opacity=".5"/>
7
+ <path d="M96 156h82c34 0 30 24 72 24h46" opacity=".7"/>
8
+ <path d="M96 204h82c34 0 30-24 72-24h46"/>
9
+ <path d="M96 252h62c48 0 44-72 94-72h44" opacity=".5"/>
10
+ <path d="m278 158 24 22-24 22" stroke="#f7be74"/>
11
+ </g>
12
+ <text x="366" y="185" fill="#f5f3ec" font-family="Helvetica Neue,Arial,sans-serif" font-size="98" font-weight="650" letter-spacing="-5">Relay</text>
13
+ <text x="371" y="239" fill="#acb9b0" font-family="Helvetica Neue,Arial,sans-serif" font-size="27" letter-spacing=".2">Keep Codex moving.</text>
14
+ <text x="1140" y="73" text-anchor="end" fill="#a8d9bd" font-family="Menlo,monospace" font-size="15">FOR YOUR TERMINAL</text>
15
+ </svg>
package/bin/relay.cjs ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
8
+ const { spawnSync, spawn } = require('node:child_process');
9
+ const { setTimeout: delay } = require('node:timers/promises');
10
+
11
+ const packageRoot = path.resolve(__dirname, '..');
12
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
13
+
14
+ function stateDirectory(env = process.env, home = os.homedir()) {
15
+ if (env.RELAY_STATE || env.CODEX_ACCOUNTS_STATE) {
16
+ return path.resolve(env.RELAY_STATE || env.CODEX_ACCOUNTS_STATE);
17
+ }
18
+ const support = path.join(home, 'Library', 'Application Support');
19
+ const legacy = path.join(support, 'Codex Accounts');
20
+ return fs.existsSync(path.join(legacy, 'accounts')) ? legacy : path.join(support, 'Relay');
21
+ }
22
+
23
+ function pythonCommand() {
24
+ const choices = [process.env.RELAY_PYTHON, 'python3', 'python3.14', 'python3.13', 'python3.12', 'python3.11'].filter(Boolean);
25
+ for (const command of choices) {
26
+ const result = spawnSync(command, ['-c', 'import sys; print(sys.executable); raise SystemExit(sys.version_info < (3, 11))'], { encoding: 'utf8' });
27
+ if (result.status === 0) return result.stdout.trim();
28
+ }
29
+ throw new Error('Relay needs Python 3.11 or newer. Install it with: brew install python@3.13');
30
+ }
31
+
32
+ function privateDirectory(directory) {
33
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
34
+ const info = fs.lstatSync(directory);
35
+ if (info.isSymbolicLink() || (process.getuid && info.uid !== process.getuid())) {
36
+ throw new Error('Relay needs a private folder owned by your user.');
37
+ }
38
+ fs.chmodSync(directory, 0o700);
39
+ }
40
+
41
+ function fingerprint() {
42
+ const source = path.join(packageRoot, 'python');
43
+ const names = fs.readdirSync(source).filter(name => /\.(py|swift|lock|txt)$/.test(name)).sort();
44
+ for (const name of fs.readdirSync(path.join(packageRoot, 'tests')).filter(name => name.endsWith('.py')).sort()) names.push(`tests/${name}`);
45
+ const hash = crypto.createHash('sha256');
46
+ hash.update(pkg.version);
47
+ for (const name of names) hash.update(name).update(fs.readFileSync(name.startsWith('tests/') ? path.join(packageRoot, name) : path.join(source, name)));
48
+ return { id: `${pkg.version}-${hash.digest('hex').slice(0, 16)}`, names, source };
49
+ }
50
+
51
+ function processBirth(pid) {
52
+ const result = spawnSync('/bin/ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8' });
53
+ return result.status === 0 ? result.stdout.trim() : '';
54
+ }
55
+
56
+ function ownerAlive(owner) {
57
+ try {
58
+ process.kill(owner.pid, 0);
59
+ return Boolean(owner.birth) && processBirth(owner.pid) === owner.birth;
60
+ } catch { return false; }
61
+ }
62
+
63
+ async function withSetupLock(state, operation) {
64
+ const lock = path.join(state, 'relay-setup.lock');
65
+ const deadline = Date.now() + 10 * 60 * 1000;
66
+ while (true) {
67
+ try { fs.mkdirSync(lock, { mode: 0o700 }); break; }
68
+ catch (error) {
69
+ if (error.code !== 'EEXIST') throw error;
70
+ let stale = false;
71
+ try {
72
+ const owner = JSON.parse(fs.readFileSync(path.join(lock, 'owner.json'), 'utf8'));
73
+ stale = !ownerAlive(owner);
74
+ } catch {
75
+ // Another process may be between mkdir and writing its owner record.
76
+ try { stale = Date.now() - fs.statSync(lock).mtimeMs > 30_000; }
77
+ catch (error) { if (error.code === 'ENOENT') continue; throw error; }
78
+ }
79
+ if (stale) {
80
+ const abandoned = `${lock}.stale-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
81
+ try { fs.renameSync(lock, abandoned); fs.rmSync(abandoned, { recursive: true, force: true }); }
82
+ catch (error) { if (!['ENOENT', 'EEXIST'].includes(error.code)) throw error; }
83
+ continue;
84
+ }
85
+ if (Date.now() > deadline) throw new Error('Another Relay setup is still running. Let it finish, then try again.');
86
+ await delay(200);
87
+ }
88
+ }
89
+ fs.writeFileSync(path.join(lock, 'owner.json'), JSON.stringify({ pid: process.pid, birth: processBirth(process.pid) }), { mode: 0o600 });
90
+ try { return await operation(); }
91
+ finally { fs.rmSync(lock, { recursive: true, force: true }); }
92
+ }
93
+
94
+ function run(command, args) {
95
+ const result = spawnSync(command, args, { stdio: 'inherit' });
96
+ if (result.error || result.status !== 0) throw new Error('Setup did not finish. Run Relay again to retry.');
97
+ }
98
+
99
+ async function prepare(state) {
100
+ const bundle = fingerprint();
101
+ const destination = path.join(state, 'relay-runtimes', bundle.id);
102
+ const interpreter = path.join(destination, '.venv', 'bin', 'python');
103
+ const marker = path.join(destination, 'ready.json');
104
+ const ready = () => fs.existsSync(marker) && fs.existsSync(interpreter) && spawnSync(interpreter, ['-c', 'import websockets, jsonschema'], { stdio: 'ignore' }).status === 0;
105
+ if (ready()) return { interpreter, router: path.join(destination, 'router.py') };
106
+ const python = pythonCommand();
107
+ return withSetupLock(state, async () => {
108
+ if (ready()) return { interpreter, router: path.join(destination, 'router.py') };
109
+ privateDirectory(destination);
110
+ for (const name of bundle.names) {
111
+ const source = name.startsWith('tests/') ? path.join(packageRoot, name) : path.join(bundle.source, name);
112
+ fs.mkdirSync(path.dirname(path.join(destination, name)), { recursive: true, mode: 0o700 });
113
+ fs.copyFileSync(source, path.join(destination, name));
114
+ }
115
+ console.error('Preparing Relay for this Mac. This only happens on first use or after an update.');
116
+ // Always retry dependency installation if a previous setup was interrupted.
117
+ run(python, ['-m', 'venv', path.join(destination, '.venv')]);
118
+ run(interpreter, ['-m', 'pip', 'install', '--disable-pip-version-check', '-r', path.join(destination, 'requirements.lock')]);
119
+ fs.writeFileSync(marker, JSON.stringify({ version: pkg.version }), { mode: 0o600 });
120
+ return { interpreter, router: path.join(destination, 'router.py') };
121
+ });
122
+ }
123
+
124
+ async function main() {
125
+ if (process.platform !== 'darwin') throw new Error('Relay currently supports macOS.');
126
+ if (process.argv.length === 3 && ['--version', '-V'].includes(process.argv[2])) {
127
+ console.log(`Relay ${pkg.version}`); return;
128
+ }
129
+ const state = stateDirectory(); privateDirectory(state);
130
+ const runtime = await prepare(state);
131
+ const child = spawn(runtime.interpreter, [runtime.router, ...process.argv.slice(2)], {
132
+ stdio: 'inherit', env: { ...process.env, RELAY_STATE: state, CODEX_ACCOUNTS_STATE: state }
133
+ });
134
+ // Terminal Ctrl-C already reaches both foreground processes.
135
+ const interrupt = () => {};
136
+ const terminate = () => child.kill('SIGTERM');
137
+ process.on('SIGINT', interrupt); process.on('SIGTERM', terminate);
138
+ process.on('SIGHUP', terminate);
139
+ child.on('error', () => { console.error('Relay could not start. Run it again to retry.'); process.exitCode = 1; });
140
+ await new Promise(resolve => child.on('close', (code, signal) => {
141
+ process.exitCode = code ?? (signal === 'SIGINT' ? 130 : 1); resolve();
142
+ }));
143
+ process.off('SIGINT', interrupt); process.off('SIGTERM', terminate);
144
+ process.off('SIGHUP', terminate);
145
+ }
146
+
147
+ module.exports = { stateDirectory, fingerprint, ownerAlive, withSetupLock, prepare };
148
+ if (require.main === module) main().catch(error => { console.error(`relay: ${error.message}`); process.exitCode = 1; });
@@ -0,0 +1,176 @@
1
+ # Commands
2
+
3
+ ## Help
4
+
5
+ ```sh
6
+ relay help
7
+ relay help account
8
+ relay help workspace
9
+ relay run --help
10
+ relay run -- exec --help
11
+ relay help codex resume
12
+ ```
13
+
14
+ `relay run --help` shows the installed Codex command and flag list, followed by
15
+ Relay's launch options. Put native arguments after `relay run --`.
16
+
17
+ ## Accounts
18
+
19
+ ```sh
20
+ relay setup --count 1
21
+ relay setup --count 6
22
+ relay add personal
23
+ relay list
24
+ relay account identify
25
+ relay refresh
26
+ relay account rename personal "My account"
27
+ relay account disable personal
28
+ relay account enable personal
29
+ relay account priority personal 10
30
+ relay account cap personal 2
31
+ relay account models personal YOUR_MODEL
32
+ relay account reauth personal
33
+ relay account remove personal
34
+ ```
35
+
36
+ Accounts display their saved email addresses. `account identify` fills in emails
37
+ for earlier installations without another sign-in. Commands accept an account's
38
+ label, unique email, or chosen display name. Renaming keeps the login usable.
39
+ A session cap of zero means unlimited. Removing an account retains its login
40
+ unless you request `--revoke`.
41
+
42
+ ## Launch and recovery
43
+
44
+ ```sh
45
+ relay run
46
+ relay run -- resume --yolo
47
+ relay run -- -C ~/projects/website resume
48
+ relay run -- -p work --add-dir ~/shared
49
+ relay run -- --worktree "Try the next change"
50
+ relay run -- exec --json "Run the checks"
51
+ relay run -- review --uncommitted
52
+ relay explain -- resume --yolo
53
+ relay run --require-recovery -- resume
54
+ ```
55
+
56
+ Resume defaults to the current folder. Add `--all` to browse other folders or
57
+ `-C/--cd` to choose another working folder. Native profile files are read from
58
+ `$CODEX_HOME/NAME.config.toml`; explicit config flags override the profile.
59
+ Relay worktrees are detached Git worktrees under its private state folder. Their
60
+ files are retained for review; cleanup does not delete worktrees.
61
+
62
+ Interactive work, resume/fork, profiles, extra directories, worktrees, and supported
63
+ noninteractive exec/review runs use the account pool. Exec supports text/stdin,
64
+ images, JSON events, output schemas, last-message files, and resume/fork. Review
65
+ supports the working tree, base branch, commit, or custom instructions.
66
+
67
+ Native utility commands are forwarded unchanged. Local providers, explicitly
68
+ remote servers, hook-trust bypass, and ignoring native config/rules use native
69
+ Codex directly with its ordinary login. Help/version also run directly. These
70
+ modes do not use Relay recovery. `--require-recovery` rejects such a launch, and
71
+ `explain` shows which mode applies without making an inference request.
72
+
73
+ Noninteractive runs cannot approve a tool request on your behalf. Use explicit
74
+ native permissions such as `--yolo` only when intended, or resume interactively
75
+ when input is required. Automatic review is supported by `--approve-for-me`.
76
+
77
+ ## Dashboard and session controls
78
+
79
+ ```sh
80
+ relay status
81
+ relay status --watch
82
+ relay status --all
83
+ relay status --json
84
+ relay menubar
85
+ relay menubar --no-startup
86
+ relay attach SESSION_ID
87
+ relay attach SESSION_ID --takeover
88
+ relay pause SESSION_ID --wait
89
+ relay unpause SESSION_ID --wait
90
+ relay retry SESSION_ID --wait
91
+ relay switch SESSION_ID --account personal --wait
92
+ relay stop SESSION_ID --wait
93
+ relay restart SESSION_ID
94
+ ```
95
+
96
+ The watch view waits for local file changes and redraws only when useful status
97
+ changes. Heartbeats do not repeatedly print the table. Watching uses no model
98
+ allowance and does not fetch account allowance. `refresh` explicitly checks it.
99
+ The menu uses one local status stream, a template icon, and native session actions.
100
+ It opens at Mac login after `relay menubar`; disable that in its menu or with
101
+ `relay menubar --no-startup --configure-only`.
102
+
103
+ IDs may be shortened to a unique prefix. Without `--wait`, controls are queued;
104
+ with it, Relay waits for the worker's confirmation and reports a rejection.
105
+ Pause stops automatic recovery after current work finishes. Retry clears a review
106
+ hold: inspect an interrupted external action in the conversation first.
107
+
108
+ ## Background tasks and saved workspaces
109
+
110
+ ```sh
111
+ relay start --name website --cwd ~/projects/website --prompt "Run the checks"
112
+ relay workspace save daily
113
+ relay workspace list
114
+ relay workspace restore daily
115
+ relay workspace remove daily
116
+ relay startup enable SESSION_ID
117
+ relay startup list
118
+ relay startup disable SESSION_ID
119
+ ```
120
+
121
+ A workspace saves the same supervised session IDs, rather than starting fresh
122
+ conversations. Restore restarts stopped jobs and prints reconnect commands; live
123
+ jobs are retained. Save specific sessions by appending their IDs to `workspace save`.
124
+
125
+ The normal command remains available after reboot. Background task recovery at
126
+ login is a separate opt-in setting; enabling the menu does not enable task startup.
127
+ Uncertain work still pauses for review after recovery.
128
+
129
+ ## Preferences
130
+
131
+ ```sh
132
+ relay config
133
+ relay config notifications true
134
+ relay config background_on_disconnect true
135
+ relay config keep_awake true
136
+ relay config retry_interval 300
137
+ relay config retry_attempts 3
138
+ relay config routing quota
139
+ relay config preferred_accounts '["personal"]'
140
+ relay config fallback_models '["YOUR_CHOSEN_MODEL"]'
141
+ relay config retry_interval 60 --profile quick
142
+ relay run --profile quick
143
+ ```
144
+
145
+ Routing choices are `balanced`, `quota`, `priority`, and `round-robin`. Model
146
+ fallback is opt-in. Capacity errors otherwise retain the selected model/account.
147
+ Keep-awake prevents idle system sleep only while a managed task is active; it does
148
+ not keep the display on. Project preferences may live in `.relay.toml` under
149
+ `[settings]` or `[profiles.NAME]`. Legacy `.codex-accounts.toml` files still work.
150
+ Relay recovery profiles go before `--`; native Codex profiles go after it.
151
+
152
+ ## Diagnostics and cleanup
153
+
154
+ ```sh
155
+ relay doctor --self-test
156
+ relay doctor --backup
157
+ relay doctor --restore-backup
158
+ relay diagnostics relay-diagnostics.json
159
+ relay clean
160
+ relay clean --older-than 30
161
+ relay clean --apply
162
+ relay events --limit 30
163
+ relay usage
164
+ relay runtime list
165
+ relay runtime preflight /path/to/codex
166
+ ```
167
+
168
+ Diagnostics export allowlisted versions, event types, and counts without account
169
+ emails, task names, project paths, prompts, or credentials. Review other files
170
+ before sharing them; private state and connection files must stay private.
171
+
172
+ Cleanup is a dry run unless `--apply` is given. It retains live/restartable runtime
173
+ references and saved workspaces. `--jobs` also allows removal of old stopped-job
174
+ records. Stop supervised jobs before applying cleanup. Worktrees and account
175
+ logins are retained. Metadata restore also requires stopped tasks and preserves a
176
+ copy of the replaced database. Usage forecasts are local estimates.
@@ -0,0 +1,72 @@
1
+ # Sharing Relay
2
+
3
+ Relay is published on npm as [relay-codex](https://www.npmjs.com/package/relay-codex).
4
+ Friends can run `npx relay-codex` or install it with `npm install -g relay-codex`.
5
+
6
+ ## GitHub and npx
7
+
8
+ The GitHub repository is enough for friends to install or try Relay:
9
+
10
+ ```sh
11
+ npm install -g github:blackspider-ops/relay#v0.5.0
12
+ npx --yes github:blackspider-ops/relay#v0.5.0 --version
13
+ ```
14
+
15
+ `npx` runs packages; it is not a separate publishing service. Publishing
16
+ `relay-codex` on npm also makes `npx relay-codex` available.
17
+
18
+ ## Publish on npm
19
+
20
+ The repository includes `.github/workflows/publish.yml`. Configure a trusted
21
+ publisher for `relay-codex` using GitHub user `blackspider-ops`, repository `relay`,
22
+ and workflow filename `publish.yml`. Select direct `npm publish` permission if
23
+ releases should publish automatically. No long-lived publishing token is needed.
24
+ A new trusted publisher must complete its first publish within npm's validation
25
+ window. Run the workflow for an existing reviewed version tag, or publish the
26
+ GitHub release; the workflow runs checks before publishing. After the first
27
+ successful trusted publish, set the repository variable
28
+ `NPM_TRUSTED_PUBLISHING_READY=true` to enable automatic release publishing. Until
29
+ then, releases do not launch a publishing attempt that requires authentication.
30
+
31
+ Initial trust setup requires npm account verification. Interactive local
32
+ `npm publish` also requires verification when package 2FA applies. Installing and
33
+ running the public package does not require npm sign-in. An expired publishing
34
+ prompt should be restarted once, rather than repeatedly running `npm login`.
35
+
36
+ For a manual release, review and publish the exact packed artifact:
37
+
38
+ ```sh
39
+ npm test
40
+ npm run check
41
+ npm pack --ignore-scripts
42
+ npm publish ./relay-codex-VERSION.tgz --access public
43
+ ```
44
+
45
+ After publishing, friends can use:
46
+
47
+ ```sh
48
+ npx relay-codex setup --count 2
49
+ npm install -g relay-codex
50
+ ```
51
+
52
+ ## Homebrew
53
+
54
+ The repository includes a formula under `Formula/`. It can be installed from this
55
+ repository as a personal tap; it does not need acceptance into Homebrew's main
56
+ collection. The formula must point to a tagged release archive with its actual
57
+ SHA-256 checksum.
58
+
59
+ ```sh
60
+ brew tap blackspider-ops/relay https://github.com/blackspider-ops/relay
61
+ brew install blackspider-ops/relay/relay-codex
62
+ ```
63
+
64
+ For a new release, update the package and Python versions together, run the checks,
65
+ tag it, upload the archive, then update the formula URL and checksum. Keep the
66
+ previous release available for users who need to roll back.
67
+
68
+ Official references: [trusted publishing](https://docs.npmjs.com/trusted-publishers/),
69
+ [package publishing verification](https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification/),
70
+ [npm publishing](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages),
71
+ [npx](https://docs.npmjs.com/cli/v11/commands/npx/), and
72
+ [Homebrew taps](https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap).
@@ -0,0 +1,49 @@
1
+ # Troubleshooting
2
+
3
+ **“This conversation is open in another app.”**
4
+
5
+ Close or unload that conversation in its original app before resuming it. Or fork
6
+ it into a separate conversation. For a Relay-owned task, use
7
+ `relay attach --takeover SESSION_ID`.
8
+
9
+ **A session needs attention.**
10
+
11
+ Run `relay status`, then `relay events`. Once the cause is resolved, use
12
+ `relay restart SESSION_ID` if the worker stopped, or `relay retry SESSION_ID` if it
13
+ is still running and you have reviewed the interrupted work.
14
+
15
+ **Setup was interrupted.**
16
+
17
+ Run the command again. Relay retries unfinished dependency installation. If Python
18
+ is missing or too old, install it with `brew install python@3.13`.
19
+
20
+ **An account needs another sign-in.**
21
+
22
+ Move its live tasks to another account, then run `relay account reauth ACCOUNT`.
23
+ Other accounts can keep working.
24
+
25
+ **The terminal disconnected after a worker restart.**
26
+
27
+ Relay reconnects the terminal when a replacement worker publishes a recovered
28
+ connection. If recovery requires review or the project folder is unavailable,
29
+ check `relay status` and `relay events`, then reattach with `relay attach SESSION_ID`.
30
+
31
+ **Something still seems wrong.**
32
+
33
+ Run `relay doctor --self-test`. Include the Relay and Codex versions when reporting
34
+ a bug. Do not post auth files, tokens, connection files, or private task prompts.
35
+
36
+ Long runs, physical sleep/wake cycles, real revoked logins, and future Codex
37
+ versions need broader live testing. The tests do not remove those limits.
38
+
39
+ **The dashboard repeats or an email is missing.**
40
+
41
+ Check `relay --version` and update Relay. Watch now prints only meaningful changes
42
+ from local state, without using model allowance. Earlier enrolled accounts can be
43
+ identified with `relay account identify`; this reads saved login metadata locally.
44
+
45
+ **Why does npm ask for authentication?**
46
+
47
+ That verification protects package publishing. Installing and running the public
48
+ Relay package does not require npm sign-in. Maintainers can use the repository's
49
+ trusted-publishing workflow once npm has authorized that workflow.
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "relay-codex",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.0",
4
+ "description": "Keep your Codex sessions moving between your own accounts.",
5
+ "license": "MIT",
6
+ "author": "blackspider-ops",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/blackspider-ops/relay.git"
10
+ },
11
+ "homepage": "https://github.com/blackspider-ops/relay#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/blackspider-ops/relay/issues"
14
+ },
15
+ "bin": {
16
+ "relay": "bin/relay.cjs"
17
+ },
18
+ "files": [
19
+ "bin/*.cjs",
20
+ "python/*.py",
21
+ "python/*.swift",
22
+ "python/*.lock",
23
+ "python/*.txt",
24
+ "tests/*.py",
25
+ "README.md",
26
+ "LICENSE",
27
+ "docs/*.md",
28
+ "assets/*.svg"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "os": [
34
+ "darwin"
35
+ ],
36
+ "keywords": [
37
+ "codex",
38
+ "terminal",
39
+ "accounts",
40
+ "macos"
41
+ ],
42
+ "scripts": {
43
+ "test": "node --test tests/bootstrap.test.cjs",
44
+ "check": "node scripts/check-release.cjs"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }