@workweave/router 0.2.6 → 0.2.8
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 +29 -9
- package/cc-statusline.sh +68 -6
- package/codex-skills/disable-routing/SKILL.md +22 -0
- package/install.sh +224 -27
- package/package.json +2 -1
- package/pi-router/README.md +30 -9
- package/pi-router/src/compaction.ts +144 -20
- package/pi-router/src/config.ts +5 -2
- package/pi-router/src/force-model.ts +126 -0
- package/pi-router/src/index.ts +6 -4
- package/pi-router/src/pricing.generated.ts +81 -0
- package/pi-router/src/routed-model.ts +151 -17
- package/pi-router/src/savings.ts +191 -0
- package/pi-router/src/ui.ts +91 -0
- package/pi-router/src/wooly.ts +408 -0
- package/uninstall.sh +25 -0
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# @workweave/router
|
|
2
2
|
|
|
3
|
-
One command, anywhere, to point Claude Code, Codex, or
|
|
3
|
+
One command, anywhere, to point Claude Code, Codex, opencode, or pi at the Weave Router.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx @workweave/router # interactive: pick Claude Code / Codex / opencode, then scope
|
|
6
|
+
npx @workweave/router # interactive: pick Claude Code / Codex / opencode / pi, then scope
|
|
7
7
|
npx @workweave/router --claude # skip the picker, target Claude Code
|
|
8
8
|
npx @workweave/router --codex # skip the picker, target the OpenAI Codex CLI
|
|
9
9
|
npx @workweave/router --opencode # skip the picker, target opencode
|
|
10
|
+
npx @workweave/router --pi # skip the picker, target pi + Loom UI
|
|
10
11
|
npx @workweave/router --scope project # per-repo install, commit settings.json (or .codex/ / opencode.json)
|
|
11
12
|
npx @workweave/router --local # self-hosted via docker-compose (localhost:8080)
|
|
12
13
|
npx @workweave/router --base-url https://router.acme.internal
|
|
@@ -31,6 +32,9 @@ npx @workweave/router status --codex # is Codex on the router or direct?
|
|
|
31
32
|
Claude Code reads its router setting at launch, so quit and reopen it after an
|
|
32
33
|
on/off. Codex and opencode pick it up on their next run. Inside Claude Code the
|
|
33
34
|
slash commands `/router-off`, `/router-on`, and `/router-status` do the same.
|
|
35
|
+
Codex installs a `$disable-routing` skill that switches its next session back
|
|
36
|
+
to the normal provider; Codex does not support third-party `/disable-routing`
|
|
37
|
+
slash commands. The shell equivalent is `npx @workweave/router disable-routing`.
|
|
34
38
|
Cursor has no config file we own — toggle its base URL override in **Settings →
|
|
35
39
|
Models** instead.
|
|
36
40
|
|
|
@@ -40,6 +44,7 @@ Uninstall:
|
|
|
40
44
|
npx @workweave/router --uninstall # Claude Code, user scope
|
|
41
45
|
npx @workweave/router --uninstall --codex # Codex, user scope
|
|
42
46
|
npx @workweave/router --uninstall --opencode # opencode, user scope
|
|
47
|
+
npx @workweave/router --uninstall --pi # pi, user scope
|
|
43
48
|
npx @workweave/router --uninstall --scope project # Claude Code, inside the repo
|
|
44
49
|
npx @workweave/router --uninstall --codex --scope project
|
|
45
50
|
```
|
|
@@ -52,7 +57,7 @@ Node ≥ 18 — no `curl | sh`, no Git clone, no PATH fiddling. Everything the
|
|
|
52
57
|
shell installer documents (targets, scopes, flags, environment variables)
|
|
53
58
|
works identically here.
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
Four install targets:
|
|
56
61
|
|
|
57
62
|
- **Claude Code** (default) — patches `~/.claude/settings.json` (or
|
|
58
63
|
`<repo>/.claude/settings.json` with `--scope project`) so `claude` routes
|
|
@@ -60,10 +65,19 @@ Three install targets:
|
|
|
60
65
|
api.anthropic.com.
|
|
61
66
|
- **Codex** (`--codex`) — patches `~/.codex/config.toml` (or
|
|
62
67
|
`<repo>/.codex/config.toml`) with a managed `[model_providers.weave]`
|
|
63
|
-
block plus `model_provider = "weave"`.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
block plus `model_provider = "weave"`. The provider preserves the existing
|
|
69
|
+
ChatGPT OAuth login. The public hosted endpoint sends
|
|
70
|
+
`X-Weave-Router-Strategy: hmm`; `--local` and custom self-hosted URLs keep
|
|
71
|
+
their router's configured default because its HMM sidecar is optional. HMM or forced
|
|
72
|
+
`gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` turns use that plan;
|
|
73
|
+
every other selected model uses its WorkWeave deployment or BYOK credential.
|
|
74
|
+
The block lives between begin/end markers
|
|
75
|
+
so re-running the installer rewrites it cleanly and `--uninstall --codex`
|
|
76
|
+
removes it without touching the rest of your config. Codex does not load
|
|
77
|
+
third-party slash-command files; to send a router directive, type it with
|
|
78
|
+
one leading space (for example, ` /force-model gpt-5.6-terra`). Its
|
|
79
|
+
`$disable-routing` skill returns the next Codex session to the default
|
|
80
|
+
provider without logging out or deleting the router configuration.
|
|
67
81
|
- **opencode** (`--opencode`) — merges a `provider.weave` entry (backed by
|
|
68
82
|
opencode's built-in `@ai-sdk/anthropic` provider) into
|
|
69
83
|
`~/.config/opencode/opencode.json` (or `<repo>/opencode.json` with
|
|
@@ -71,6 +85,12 @@ Three install targets:
|
|
|
71
85
|
natively, so opencode talks to it unmodified. Re-install rewrites only
|
|
72
86
|
the managed `provider.weave` block; `--uninstall --opencode` strips it
|
|
73
87
|
and leaves your other providers and settings alone.
|
|
88
|
+
- **pi** (`--pi`) — registers the `weave` provider and installs this package as
|
|
89
|
+
a pi extension. Stock pi then gets the Loom startup header, Wooly's animated
|
|
90
|
+
mascot, the persistent actual-route display, cumulative session savings,
|
|
91
|
+
`/fm` + `/ufm` model-pin commands with a `[forced]` status, and the
|
|
92
|
+
context-isolated `dispatch` tool. There is no forked pi binary and no separate
|
|
93
|
+
Loom runtime.
|
|
74
94
|
|
|
75
95
|
See the [main installer docs](https://github.com/workweave/router/tree/main/install)
|
|
76
96
|
for the full reference.
|
|
@@ -79,8 +99,8 @@ for the full reference.
|
|
|
79
99
|
|
|
80
100
|
- Node ≥ 18 (ships with `npx`)
|
|
81
101
|
- `bash` on PATH (macOS / Linux native; Windows needs Git Bash or WSL)
|
|
82
|
-
- `jq` on PATH — used by the Claude Code status line
|
|
83
|
-
|
|
102
|
+
- `jq` on PATH — used by the Claude Code status line and the opencode/pi JSON
|
|
103
|
+
merges. Not required for the Codex path.
|
|
84
104
|
|
|
85
105
|
## Why npx
|
|
86
106
|
|
package/cc-statusline.sh
CHANGED
|
@@ -33,10 +33,21 @@ set -euo pipefail
|
|
|
33
33
|
# lives in their per-user cache dir, and on no-content-change days we skip
|
|
34
34
|
# the mv entirely so the repo working tree stays clean. When upstream does
|
|
35
35
|
# change, the first teammate's commit propagates the new version to the rest.
|
|
36
|
+
# A pricing-table miss also triggers a refresh off-schedule — see
|
|
37
|
+
# weave_refresh_on_price_miss below.
|
|
36
38
|
#
|
|
37
39
|
# Opt out entirely with `export WEAVE_STATUSLINE_UPDATE=0`. Override the
|
|
38
40
|
# source with `WEAVE_STATUSLINE_URL=...`, e.g. for self-hosters who fork.
|
|
41
|
+
#
|
|
42
|
+
# $1 is an optional stamp suffix, so a caller refreshing for a reason other than
|
|
43
|
+
# "the interval elapsed" rate-limits on its own clock rather than sharing the
|
|
44
|
+
# periodic check's budget. $2=1 drops the stamp again if the download fails, for
|
|
45
|
+
# callers recovering from a known-bad state where waiting out the full interval
|
|
46
|
+
# on a transient network error is worse than retrying next turn.
|
|
39
47
|
weave_self_refresh() {
|
|
48
|
+
local stamp_suffix="${1:-}"
|
|
49
|
+
local retry_on_fail="${2:-0}"
|
|
50
|
+
|
|
40
51
|
[ "${WEAVE_STATUSLINE_UPDATE:-1}" = "0" ] && return 0
|
|
41
52
|
command -v curl >/dev/null 2>&1 || return 0
|
|
42
53
|
|
|
@@ -53,7 +64,7 @@ weave_self_refresh() {
|
|
|
53
64
|
mkdir -p "$cache_dir" 2>/dev/null || return 0
|
|
54
65
|
local script_slug
|
|
55
66
|
script_slug="$(printf '%s' "$self" | tr -c 'A-Za-z0-9._-' '_')"
|
|
56
|
-
local stamp="$cache_dir/checked-at${script_slug}"
|
|
67
|
+
local stamp="$cache_dir/checked-at${script_slug}${stamp_suffix}"
|
|
57
68
|
|
|
58
69
|
local now stamp_mtime
|
|
59
70
|
now="$(date +%s 2>/dev/null)" || return 0
|
|
@@ -77,7 +88,13 @@ weave_self_refresh() {
|
|
|
77
88
|
: > "$stamp" 2>/dev/null || return 0
|
|
78
89
|
|
|
79
90
|
local url="${WEAVE_STATUSLINE_URL:-https://raw.githubusercontent.com/workweave/router/main/install/cc-statusline.sh}"
|
|
80
|
-
|
|
91
|
+
# $$ alone is not unique: two calls can run in one invocation (the periodic
|
|
92
|
+
# check and a pricing-miss retry both fire on a cold cache) and would then
|
|
93
|
+
# curl -o into the same path and mv over each other, installing a truncated
|
|
94
|
+
# script that can never self-heal. The stamp suffix is the right key — it is
|
|
95
|
+
# what makes two callers mutually exclusive in the first place, so callers
|
|
96
|
+
# that could overlap necessarily have different suffixes.
|
|
97
|
+
local tmp="${self}.tmp.$$${stamp_suffix}"
|
|
81
98
|
(
|
|
82
99
|
# Detach stdin (CC pipes JSON to us) so curl can't accidentally consume
|
|
83
100
|
# it, and silence all output so nothing leaks into the statusline.
|
|
@@ -96,6 +113,11 @@ weave_self_refresh() {
|
|
|
96
113
|
fi
|
|
97
114
|
else
|
|
98
115
|
rm -f "$tmp"
|
|
116
|
+
# A download that never landed shouldn't spend the caller's whole
|
|
117
|
+
# interval; drop the stamp so the next turn can try again.
|
|
118
|
+
if [ "$retry_on_fail" = "1" ]; then
|
|
119
|
+
rm -f "$stamp"
|
|
120
|
+
fi
|
|
99
121
|
fi
|
|
100
122
|
) >/dev/null 2>&1 &
|
|
101
123
|
disown 2>/dev/null || true
|
|
@@ -142,7 +164,7 @@ prices='{
|
|
|
142
164
|
"claude-sonnet-4-6": 0.003,
|
|
143
165
|
"claude-sonnet-5": 0.003,
|
|
144
166
|
"deepseek/deepseek-v4-flash": 0.0001134,
|
|
145
|
-
"deepseek/deepseek-v4-pro": 0.
|
|
167
|
+
"deepseek/deepseek-v4-pro": 0.00174,
|
|
146
168
|
"gemini-2.0-flash": 0.0001,
|
|
147
169
|
"gemini-2.0-flash-lite": 0.000075,
|
|
148
170
|
"gemini-2.5-flash": 0.0003,
|
|
@@ -177,6 +199,7 @@ prices='{
|
|
|
177
199
|
"gpt-5.6-sol": 0.005,
|
|
178
200
|
"gpt-5.6-terra": 0.0025,
|
|
179
201
|
"grok-4.5": 0.002,
|
|
202
|
+
"grok-4.6": 0.002,
|
|
180
203
|
"minimax/minimax-m2.7": 0.0003,
|
|
181
204
|
"minimax/minimax-m3": 0.0003,
|
|
182
205
|
"mistralai/mistral-small-2603": 0.0002,
|
|
@@ -190,8 +213,9 @@ prices='{
|
|
|
190
213
|
"qwen/qwen3-coder-next": 0.0005,
|
|
191
214
|
"qwen/qwen3-next-80b-a3b-instruct": 0.00015,
|
|
192
215
|
"qwen/qwen3.5-flash-02-23": 0.00005,
|
|
193
|
-
"qwen/qwen3.6-35b-a3b": 0.
|
|
216
|
+
"qwen/qwen3.6-35b-a3b": 0.00015,
|
|
194
217
|
"qwen/qwen3.7-plus": 0.0004,
|
|
218
|
+
"qwen/qwen3.8-max": 0.002,
|
|
195
219
|
"xiaomi/mimo-v2.5-pro": 0.001,
|
|
196
220
|
"z-ai/glm-5": 0.001,
|
|
197
221
|
"z-ai/glm-5.1": 0.0014,
|
|
@@ -211,7 +235,7 @@ prices='{
|
|
|
211
235
|
"claude-sonnet-4-6": 0.015,
|
|
212
236
|
"claude-sonnet-5": 0.015,
|
|
213
237
|
"deepseek/deepseek-v4-flash": 0.0002791,
|
|
214
|
-
"deepseek/deepseek-v4-pro": 0.
|
|
238
|
+
"deepseek/deepseek-v4-pro": 0.00348,
|
|
215
239
|
"gemini-2.0-flash": 0.0004,
|
|
216
240
|
"gemini-2.0-flash-lite": 0.0003,
|
|
217
241
|
"gemini-2.5-flash": 0.0012,
|
|
@@ -246,6 +270,7 @@ prices='{
|
|
|
246
270
|
"gpt-5.6-sol": 0.03,
|
|
247
271
|
"gpt-5.6-terra": 0.015,
|
|
248
272
|
"grok-4.5": 0.006,
|
|
273
|
+
"grok-4.6": 0.006,
|
|
249
274
|
"minimax/minimax-m2.7": 0.0012,
|
|
250
275
|
"minimax/minimax-m3": 0.0012,
|
|
251
276
|
"mistralai/mistral-small-2603": 0.0006,
|
|
@@ -259,8 +284,9 @@ prices='{
|
|
|
259
284
|
"qwen/qwen3-coder-next": 0.0012,
|
|
260
285
|
"qwen/qwen3-next-80b-a3b-instruct": 0.0012,
|
|
261
286
|
"qwen/qwen3.5-flash-02-23": 0.00015,
|
|
262
|
-
"qwen/qwen3.6-35b-a3b": 0.
|
|
287
|
+
"qwen/qwen3.6-35b-a3b": 0.001,
|
|
263
288
|
"qwen/qwen3.7-plus": 0.0016,
|
|
289
|
+
"qwen/qwen3.8-max": 0.006,
|
|
264
290
|
"xiaomi/mimo-v2.5-pro": 0.003,
|
|
265
291
|
"z-ai/glm-5": 0.0032,
|
|
266
292
|
"z-ai/glm-5.1": 0.0044,
|
|
@@ -399,6 +425,42 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
399
425
|
) || true
|
|
400
426
|
fi
|
|
401
427
|
|
|
428
|
+
# ---------- refresh when a model isn't in the pricing table ----------
|
|
429
|
+
#
|
|
430
|
+
# A model that shipped after this copy of the script has no price entry, so the
|
|
431
|
+
# jq guard above zeroes savings on every turn and the line reads "saved $0.00"
|
|
432
|
+
# — indistinguishable from "the router ran and didn't beat your selection". The
|
|
433
|
+
# periodic check heals that eventually, but a week late, and a model launch is
|
|
434
|
+
# exactly when the number gets looked at. Refresh off-schedule instead.
|
|
435
|
+
#
|
|
436
|
+
# Keyed on its own stamp per unpriced id: a model we never price (self-hosted,
|
|
437
|
+
# unrecognized) then costs one download per interval, not one per turn, and
|
|
438
|
+
# can't starve the periodic check.
|
|
439
|
+
weave_refresh_on_price_miss() {
|
|
440
|
+
local candidates="" m
|
|
441
|
+
for m in "$@"; do
|
|
442
|
+
case "$m" in
|
|
443
|
+
"" | "?" | failure | weave-router | "<synthetic>") continue ;;
|
|
444
|
+
esac
|
|
445
|
+
candidates="${candidates}${m}"$'\n'
|
|
446
|
+
done
|
|
447
|
+
[ -n "$candidates" ] || return 0
|
|
448
|
+
|
|
449
|
+
# A malformed $prices emits nothing here, which fails closed (no refresh)
|
|
450
|
+
# rather than re-downloading every turn.
|
|
451
|
+
local missing
|
|
452
|
+
missing="$(printf '%s' "$candidates" \
|
|
453
|
+
| jq -rR --argjson p "$prices" \
|
|
454
|
+
'select($p.input[.] == null or $p.output[.] == null)' 2>/dev/null \
|
|
455
|
+
| head -n 1)" || return 0
|
|
456
|
+
[ -n "$missing" ] || return 0
|
|
457
|
+
|
|
458
|
+
local model_slug
|
|
459
|
+
model_slug="$(printf '%s' "$missing" | tr -c 'A-Za-z0-9._-' '_')"
|
|
460
|
+
weave_self_refresh ".miss.${model_slug}" 1
|
|
461
|
+
}
|
|
462
|
+
weave_refresh_on_price_miss "$requested_norm" "$routed" 2>/dev/null || true
|
|
463
|
+
|
|
402
464
|
# Brand color (#FF6C47) on terminals that grok 24-bit truecolor — that's
|
|
403
465
|
# every modern one (iTerm2, Apple Terminal, vscode, ghostty, alacritty,
|
|
404
466
|
# wezterm, kitty). Falls back gracefully on any escape-stripping terminal.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: disable-routing
|
|
3
|
+
description: "Switch Codex back to its normal provider for future sessions."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed disable-routing skill -->
|
|
7
|
+
|
|
8
|
+
# Disable Weave routing
|
|
9
|
+
|
|
10
|
+
When the user invokes `$disable-routing`, switch the current Codex installation
|
|
11
|
+
off the Weave Router without logging out or deleting its router configuration.
|
|
12
|
+
|
|
13
|
+
1. Explain that the change takes effect on the next `codex` launch and that it
|
|
14
|
+
can later be reversed with `npx --package @workweave/router -y -- weave-router on --codex{{SCOPE}}`.
|
|
15
|
+
2. Run exactly:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx --package @workweave/router -y -- weave-router off --codex{{SCOPE}}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
3. Report the command result. Do not uninstall the router or alter any other
|
|
22
|
+
Codex settings.
|