Skip to content

Commit a0430a6

Browse files
committed
Initial release: ReRouted 0.3.1
0 parents  commit a0430a6

68 files changed

Lines changed: 18740 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
dist/
3+
capture-out/
4+
*.log
5+
.DS_Store
6+
config.json
7+
.scratch/

AGENTS.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# ReRouted repository instructions
2+
3+
## Non-negotiable definition of done
4+
5+
Never describe a fix, feature, refactor, UI pass, documentation update, or other iteration as "done" until every item below is true:
6+
7+
1. The relevant automated tests pass.
8+
2. The package version is bumped so the shipped app has a unique identity.
9+
3. The change is committed on a branch and pushed to `origin`.
10+
4. The branch is merged into `main` and the merged `main` is pushed.
11+
5. The Apple Silicon DMG is rebuilt from that exact merged `main` commit.
12+
6. The newest DMG is transferred to and installed on the host named `macair`.
13+
7. `/Applications/ReRouted.app` is launched on `macair` and its installed version is verified.
14+
8. The final report records the version, merged commit, DMG filename, DMG SHA-256, and MacBook Air verification.
15+
16+
If any item is missing, say exactly what remains. Do not use "done" as shorthand for code-complete, tests-passing, committed, or pushed.
17+
18+
Follow [docs/release-checklist.md](docs/release-checklist.md) for commands and ordering.
19+
20+
## Repository basics
21+
22+
- Target branch: `main`.
23+
- DMG target: Apple Silicon macOS (`arm64`).
24+
- Test command: `npm test`.
25+
- Package command: `npm run package:dmg` on macOS.
26+
- Preserve user data in `~/Library/Application Support/ReRouted` or the existing lowercase `rerouted` directory during app replacement.
27+
- Do not claim broad OpenAI API compatibility beyond the routes implemented in `src/lib/gateway.js`.
28+
- Same-provider OAuth accounts use automatic fill-first fallback. Explicit combos still control
29+
cross-provider/model fallback and round-robin behavior.

README.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<div align="center">
2+
<img src="./resources/brandMark@2x.png" width="64" height="64" alt="ReRouted logo" />
3+
<h1>ReRouted</h1>
4+
<p><strong>Stop rewiring your AI tools every time an account hits quota.</strong></p>
5+
<p>
6+
A macOS menu-bar router that puts your connected accounts, models,
7+
API keys, and fallback routes behind one local chat-completions endpoint.
8+
</p>
9+
<p>
10+
<a href="https://rerouted.dev">Website</a> |
11+
<a href="https://github.com/gitcommit90/rerouted/releases/latest">Download</a> |
12+
<a href="#quick-start">Quick start</a> |
13+
<a href="./docs/architecture.md">Architecture</a>
14+
</p>
15+
<p>
16+
<a href="https://github.com/gitcommit90/rerouted/releases/latest"><img alt="GitHub release" src="https://img.shields.io/github/v/release/gitcommit90/rerouted?color=ef5b2a&label=release" /></a>
17+
<img alt="macOS Apple Silicon" src="https://img.shields.io/badge/macOS-Apple%20Silicon-1b1d18?logo=apple&logoColor=white" />
18+
<img alt="Local first" src="https://img.shields.io/badge/gateway-local--first-247454" />
19+
</p>
20+
</div>
21+
22+
<p align="center">
23+
<img src="./docs/images/status.png" width="400" alt="ReRouted status panel" />
24+
<img src="./docs/images/route-editor.png" width="400" alt="ReRouted route editor" />
25+
</p>
26+
27+
## One URL. The routing decision lives somewhere sane.
28+
29+
Your editor should not need to know which account still has quota, which provider is having a bad morning, or which model you want to try next.
30+
31+
ReRouted gives compatible chat-completions clients the same local contract:
32+
33+
```text
34+
Base URL http://127.0.0.1:4949/v1
35+
API key rr-your-generated-key
36+
Model coding
37+
```
38+
39+
`coding` is a route you own. Put your preferred model first, another account second, and a backup provider third. When an upstream rate-limits, times out, or returns a retryable failure before output begins, ReRouted advances through the route without changing the URL or model name your client uses.
40+
41+
The promise is deliberately focused: ReRouted exposes model discovery and OpenAI-style chat completions. It is a routing layer, not a clone of every OpenAI API.
42+
43+
## Why ReRouted exists
44+
45+
| Without ReRouted | With ReRouted |
46+
| --- | --- |
47+
| Provider URLs and credentials are repeated across tools | One localhost URL and one generated gateway key |
48+
| A model name hard-codes a provider or account | A named route describes intent: `coding`, `fast`, `review` |
49+
| Quota means stopping to edit settings | The next route member is attempted automatically |
50+
| Multiple OAuth accounts are managed by hand | OAuth accounts share a provider pool and fall through in order |
51+
| Requests and failures are scattered | Activity, quota, token counts, and logs live in the menu bar |
52+
53+
No hosted control plane. No account with ReRouted. No Dock icon. The gateway and panel run together on your Mac.
54+
55+
## How it works
56+
57+
```text
58+
editor / agent / script
59+
|
60+
| POST /v1/chat/completions
61+
| model: "coding"
62+
v
63+
127.0.0.1:4949/v1
64+
|
65+
v
66+
ReRouted route
67+
1. primary model
68+
2. second account
69+
3. backup provider
70+
|
71+
v
72+
normalized OpenAI-style response
73+
```
74+
75+
Routes support two strategies:
76+
77+
- **Fallback:** try members in the order you chose.
78+
- **Round robin:** rotate the starting member on each request, then retain fallback through the rest.
79+
80+
Timeouts and retryable `408`, `429`, and `5xx` responses can advance the route. Streaming failures are inspected before output begins; once client-visible output has started, ReRouted does not replay the request behind the client's back.
81+
82+
## What connects
83+
84+
- **OAuth accounts:** ChatGPT, Claude, Antigravity, and xAI.
85+
- **API-key presets:** OpenRouter, NVIDIA NIM, Cloudflare, and GLM Coding.
86+
- **Custom upstreams:** any service that exposes the OpenAI chat-completions shape ReRouted expects.
87+
- **Local credential discovery:** supported credentials already stored in known files or the macOS Keychain can be imported instead of re-entered.
88+
- **Multiple accounts:** connect more than one account for the same provider and use shared or account-specific model routes.
89+
90+
OAuth accounts and keyed providers can live in the same route. ReRouted handles request translation and normalizes supported upstream responses back into the shape your client expects.
91+
92+
ReRouted is an independent project and is not affiliated with or endorsed by any upstream provider.
93+
94+
## Quick start
95+
96+
### 1. Install
97+
98+
[Download ReRouted 0.3.1 for Apple Silicon](https://github.com/gitcommit90/rerouted/releases/download/v0.3.1/ReRouted-0.3.1-arm64.dmg), open the DMG, and drag ReRouted to Applications.
99+
100+
The macOS release is Developer ID signed, notarized by Apple, and stapled for a normal Gatekeeper launch.
101+
102+
After the first install, ReRouted checks stable releases in the background. You can also use **Settings → Software updates** at any time; new versions download inside the app and install on restart.
103+
104+
### 2. Connect what you already use
105+
106+
Import a detected credential, complete an OAuth flow, or add an API key. ReRouted keeps OAuth accounts and keyed providers side by side.
107+
108+
### 3. Create a route
109+
110+
Name it for the job rather than the vendor:
111+
112+
```text
113+
coding
114+
1. preferred account and model
115+
2. second account
116+
3. backup provider
117+
```
118+
119+
### 4. Test the route, then point your client at localhost
120+
121+
Use a direct request to verify the gateway and route:
122+
123+
```bash
124+
curl http://127.0.0.1:4949/v1/chat/completions \
125+
-H "Authorization: Bearer rr-your-generated-key" \
126+
-H "Content-Type: application/json" \
127+
-d '{"model":"coding","messages":[{"role":"user","content":"Say hello in three words."}]}'
128+
```
129+
130+
Then enter the same base URL, gateway key, and route name in a configurable client that supports OpenAI-style chat completions. Setting names vary by client. Switch providers, accounts, models, and route order inside ReRouted; leave the client configuration alone.
131+
132+
## The menu-bar control plane
133+
134+
- **Status:** gateway health, endpoint, latest route, and recent traffic.
135+
- **Accounts:** OAuth sessions, imported credentials, API keys, and model availability.
136+
- **Routes:** named fallback or round-robin model groups with explicit ordering controls.
137+
- **Activity:** requests, failures, token counts, route choices, and account usage.
138+
- **Quota:** provider-specific subscription windows where supported.
139+
- **Settings:** gateway keys, localhost or network binding, login launch, security controls, and signed software updates.
140+
141+
The gateway continues running when the panel is hidden. Quitting ReRouted stops both.
142+
143+
## API surface
144+
145+
| Endpoint | Purpose |
146+
| --- | --- |
147+
| `GET /` | Same unauthenticated local health response as `/health` |
148+
| `GET /health` | Local gateway health and listening port |
149+
| `GET /v1/models` | Enabled direct models and named routes |
150+
| `POST /v1/chat/completions` | Streaming or non-streaming routed completions |
151+
152+
Requests require a generated bearer key except for `/` and `/health`. ReRouted currently targets clients that use OpenAI-style chat completions; embeddings, images, audio, and the rest of the OpenAI platform API are outside its scope.
153+
154+
## Local-first, with the boundaries stated plainly
155+
156+
- The gateway binds to `127.0.0.1` by default.
157+
- Configuration, credentials, request metadata, usage, and logs are stored locally.
158+
- Prompt bodies are not intentionally persisted.
159+
- Local config and usage files are written with restrictive permissions where supported.
160+
- Provider credentials are not encrypted at rest.
161+
- Requests and the credentials needed to authorize them are sent to the upstream services you choose.
162+
- Enabling network access binds the gateway to `0.0.0.0`; only do that on a network you trust.
163+
164+
## Build from source
165+
166+
Requires Node.js 22.12 or newer. Packaging requires macOS and produces an Apple Silicon DMG.
167+
168+
```bash
169+
git clone https://github.com/gitcommit90/rerouted.git
170+
cd rerouted
171+
npm ci
172+
npm test
173+
npm start
174+
```
175+
176+
Package the macOS app and DMG:
177+
178+
```bash
179+
npm run package:dmg
180+
```
181+
182+
The implementation is intentionally small: Electron, Node's built-in HTTP server, and a vanilla HTML/CSS/JavaScript renderer. See [the architecture document](./docs/architecture.md) for the runtime, routing, persistence, and packaging details.
183+
184+
Questions and bug reports are welcome in [GitHub Issues](https://github.com/gitcommit90/rerouted/issues).
185+
186+
## Current release
187+
188+
ReRouted `0.3.1` ships for Apple Silicon macOS with a Developer ID signature, stapled Apple notarization tickets, and in-app updates backed by stable GitHub Releases. The public API is intentionally limited to health, model discovery, and chat completions; a published third-party client compatibility matrix is still forthcoming.
189+
190+
ReRouted is released by [Public Bytes](https://publicbytes.org), a nonprofit building practical technology for public good.
191+
192+
## Thanks
193+
194+
- Thanks to [9Router](https://github.com/decolua/9router) and its contributors for pushing local multi-provider routing forward.
195+
- Thanks to [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) and its contributors for advancing local provider connectivity and the ecosystem around it.

docs/architecture.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# ReRouted architecture
2+
3+
This document describes the implementation in this repository today. It is the code map for maintainers, not a future-product proposal.
4+
5+
## Runtime shape
6+
7+
ReRouted is one Electron process with three jobs:
8+
9+
1. Own the macOS menu-bar icon and panel window.
10+
2. Run the local HTTP gateway.
11+
3. Persist provider, route, usage, and diagnostic state.
12+
13+
There is no separate daemon. Closing or hiding the panel does not stop the gateway; quitting ReRouted does.
14+
15+
```text
16+
OpenAI-compatible client
17+
|
18+
| Bearer rr-... + /v1/chat/completions
19+
v
20+
src/lib/gateway.js
21+
|
22+
| resolve model/combo
23+
v
24+
src/lib/router.js
25+
|
26+
| select provider + translate request
27+
v
28+
src/lib/providers/* ---> upstream provider API
29+
|
30+
| normalize response/SSE
31+
v
32+
OpenAI-compatible response
33+
```
34+
35+
## Main process and panel
36+
37+
`src/main.js` owns the single-instance lock, tray, frameless panel window, login-item preference, IPC handlers, store, usage store, router, and gateway lifecycle.
38+
39+
The panel is a local file loaded from `src/renderer/index.html`. `src/preload.js` exposes an IPC bridge to `src/renderer/app.js`; context isolation is enabled and renderer Node integration is disabled. The BrowserWindow sandbox is currently disabled.
40+
41+
The renderer is vanilla HTML, CSS, and JavaScript. It renders onboarding and the Status, Accounts, Routes, Activity, and Settings pages from state returned by the main process.
42+
43+
## Gateway contract
44+
45+
`src/lib/gateway.js` uses Node's built-in HTTP server.
46+
47+
| Route | Auth | Behavior |
48+
| --- | --- | --- |
49+
| `GET /` | None | Same process health response as `/health` |
50+
| `GET /health` | None | App name and current listening port |
51+
| `GET /v1/models` | Bearer key | Enabled provider models plus named route IDs |
52+
| `POST /v1/chat/completions` | Bearer key | Streaming or non-streaming routed completion |
53+
54+
The default bind is `127.0.0.1:4949`. Settings can switch the host to `0.0.0.0` for LAN or Tailscale access. CORS is currently `*`, so the bearer key is the gateway's access boundary when network binding is enabled.
55+
56+
## Model IDs and routes
57+
58+
Provider model IDs are generated by `src/lib/providers/index.js`. A direct model resolves to one enabled provider/model pair.
59+
60+
A route is a persisted virtual model with members shaped like:
61+
62+
```json
63+
{
64+
"providerId": "prov_...",
65+
"model": "upstream-model-id"
66+
}
67+
```
68+
69+
The router supports:
70+
71+
- `fallback`: members are attempted in their configured order.
72+
- `round-robin`: each request rotates the starting member, then retains fallback behavior through the remaining members.
73+
74+
Timeouts, `408`, `429`, and `5xx` responses are retryable. The per-member timeout defaults to 60 seconds. A route stops when a member returns a non-retryable failure; a single direct model stops after its only member.
75+
76+
OAuth providers add an account-pool layer beneath model routing. Accounts receive the lowest available alias (`oauth1`, `oauth2`, ...), and both shared ids (`chatgpt/gpt-5.4`) and account-specific ids (`chatgpt/oauth2/gpt-5.4`) can continue through sibling accounts. Quota failures create an account-wide lock using provider reset hints when available; authentication and transient failures use shorter model-scoped cooldowns. Early streaming quota events are inspected before the client stream starts so fallback can still occur. Selection, failure, fallback, locked-account skips, and terminal exhaustion are written as structured logs.
77+
78+
## Provider adapters
79+
80+
`src/lib/providers/index.js` selects an adapter by provider type.
81+
82+
- `openai-compat.js` handles OpenAI-shaped keyed services.
83+
- `chatgpt.js` translates chat-completion requests to the ChatGPT Codex Responses surface and normalizes Responses SSE.
84+
- `claude.js` translates OpenAI messages and tools to Anthropic Messages, applies the current OAuth client contract, and converts JSON/SSE back to OpenAI shapes.
85+
- `antigravity.js` translates Gemini-style upstream requests and SSE.
86+
- `xai.js` uses an OpenAI-compatible chat surface with xAI OAuth credentials.
87+
88+
OAuth access-token refreshes are persisted back to the provider record when an adapter returns updated tokens.
89+
90+
## OAuth and credential discovery
91+
92+
`src/lib/oauth.js` implements PKCE browser flows and loopback callbacks. Some providers require the user to paste a callback URL or code into the panel.
93+
94+
`src/lib/detect.js` performs read-only discovery of supported provider credentials already stored in known local files or the macOS Keychain.
95+
96+
Selected credentials are copied into ReRouted's config. ReRouted does not continue reading the original source on each request.
97+
98+
## Persistence
99+
100+
Electron's `userData` directory contains:
101+
102+
| File | Contents |
103+
| --- | --- |
104+
| `config.json` | Providers, credentials, models, routes, gateway keys, bind settings, onboarding state, admin password hash |
105+
| `usage.json` | Up to 20,000 recent request metadata rows and token counts |
106+
| `rerouted.log` | Gateway, OAuth, and operational diagnostics |
107+
108+
The Quota page probes subscription windows directly for ChatGPT/Codex, Claude, and Antigravity. Probe failures remain isolated per account and do not disable chat routing.
109+
110+
Config and usage writes use a temporary file followed by rename. The files are written with mode `0600`; parent directories are created with mode `0700` where supported.
111+
112+
The admin password is scrypt-hashed. Provider credentials and gateway keys are not encrypted at rest.
113+
114+
## Packaging
115+
116+
`scripts/package-mac-dmg.js` must run on macOS. It:
117+
118+
1. Packages Electron for `darwin/arm64`.
119+
2. Adds the tray resources and menu-bar-only bundle settings.
120+
3. Applies hardened-runtime Electron signing with a Developer ID identity when available.
121+
4. For release builds, notarizes and staples the app.
122+
5. For release builds, creates an updater ZIP from the stapled app and verifies the extracted bundle.
123+
6. Creates and signs a compressed UDZO DMG with an Applications shortcut.
124+
7. For release builds, notarizes and staples the DMG.
125+
126+
The output name is derived from `package.json`:
127+
128+
```text
129+
dist/ReRouted-<version>-arm64.dmg
130+
dist/ReRouted-<version>-mac-arm64.zip
131+
```
132+
133+
Official release builds require `REROUTED_NOTARY_PROFILE` and fail if Developer ID signing or notarization is unavailable. See [signing.md](signing.md).
134+
135+
Packaged builds use Electron's native macOS updater and the public stable GitHub Release feed. Checks run shortly after launch and every six hours, with a manual control in Settings. The native updater downloads the post-stapling ZIP, verifies the replacement application through macOS code signing, and installs it on restart. Draft and prerelease GitHub releases are not update channels.
136+
137+
## Tests and current gaps
138+
139+
`tests/gateway.test.js` covers password hashing, config persistence, bearer auth, model listing, streaming and non-streaming completion paths, fallback, round-robin ordering, timeouts, OAuth request behavior, token refresh, format translation, SSE decoding, multiple gateway keys, disabled models, and usage aggregation.
140+
141+
Important gaps to keep visible:
142+
143+
- No automated renderer or end-to-end Electron tests.
144+
- No macOS packaging test in CI.
145+
- No request-body size limit or gateway rate limit.
146+
- No automated release publication or CI-hosted signing/notarization.
147+
- No commit SHA embedded in the app bundle.
148+
- No compatibility matrix for third-party OpenAI clients.
149+
150+
## Maintenance rules
151+
152+
Read [docs/release-checklist.md](release-checklist.md) before changing the app. A merged commit is only one stage of a ReRouted iteration; the DMG and MacBook Air installation are part of the deliverable.

docs/images/route-editor.png

138 KB
Loading

docs/images/status.png

129 KB
Loading

0 commit comments

Comments
 (0)