@workweave/router 0.1.6 → 0.1.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 +15 -0
- package/cc-statusline.sh +12 -10
- package/commands/router-off.md +10 -0
- package/commands/router-on.md +10 -0
- package/commands/router-status.md +10 -0
- package/install.sh +457 -17
- package/package.json +1 -1
- package/uninstall.sh +21 -2
package/README.md
CHANGED
|
@@ -19,6 +19,21 @@ Version-pin for reproducible setups:
|
|
|
19
19
|
npx @workweave/router@0.1.0 --claude --scope project
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Switch on/off without uninstalling (keeps your config so switching back is
|
|
23
|
+
instant; requires an explicit client):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @workweave/router off --claude # route Claude Code directly to Anthropic
|
|
27
|
+
npx @workweave/router on --claude # route Claude Code through the router again
|
|
28
|
+
npx @workweave/router status --codex # is Codex on the router or direct?
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Claude Code reads its router setting at launch, so quit and reopen it after an
|
|
32
|
+
on/off. Codex and opencode pick it up on their next run. Inside Claude Code the
|
|
33
|
+
slash commands `/router-off`, `/router-on`, and `/router-status` do the same.
|
|
34
|
+
Cursor has no config file we own — toggle its base URL override in **Settings →
|
|
35
|
+
Models** instead.
|
|
36
|
+
|
|
22
37
|
Uninstall:
|
|
23
38
|
|
|
24
39
|
```bash
|
package/cc-statusline.sh
CHANGED
|
@@ -130,8 +130,9 @@ normalize_model() {
|
|
|
130
130
|
prices='{
|
|
131
131
|
"input": {
|
|
132
132
|
"claude-haiku-4-5": 0.0008,
|
|
133
|
-
"claude-opus-4-6": 0.
|
|
134
|
-
"claude-opus-4-7": 0.
|
|
133
|
+
"claude-opus-4-6": 0.005,
|
|
134
|
+
"claude-opus-4-7": 0.005,
|
|
135
|
+
"claude-opus-4-8": 0.005,
|
|
135
136
|
"claude-sonnet-4-5": 0.003,
|
|
136
137
|
"claude-sonnet-4-6": 0.003,
|
|
137
138
|
"deepseek/deepseek-v4-flash": 0.00014,
|
|
@@ -167,21 +168,22 @@ prices='{
|
|
|
167
168
|
"mistralai/mistral-small-2603": 0.0002,
|
|
168
169
|
"moonshotai/kimi-k2.5": 0.0006,
|
|
169
170
|
"moonshotai/kimi-k2.6": 0.00095,
|
|
170
|
-
"qwen/qwen3-235b-a22b-2507": 0.
|
|
171
|
+
"qwen/qwen3-235b-a22b-2507": 0.0002266,
|
|
171
172
|
"qwen/qwen3-30b-a3b-instruct-2507": 0.00015,
|
|
172
173
|
"qwen/qwen3-coder": 0.0009,
|
|
173
174
|
"qwen/qwen3-coder-next": 0.0005,
|
|
174
175
|
"qwen/qwen3-next-80b-a3b-instruct": 0.00015,
|
|
175
176
|
"qwen/qwen3.5-flash-02-23": 0.00005,
|
|
176
177
|
"qwen/qwen3.6-35b-a3b": 0.00015,
|
|
177
|
-
"xiaomi/mimo-v2.5": 0.0004,
|
|
178
178
|
"xiaomi/mimo-v2.5-pro": 0.001,
|
|
179
|
-
"z-ai/glm-5": 0.0006
|
|
179
|
+
"z-ai/glm-5": 0.0006,
|
|
180
|
+
"z-ai/glm-5.1": 0.00105
|
|
180
181
|
},
|
|
181
182
|
"output": {
|
|
182
183
|
"claude-haiku-4-5": 0.004,
|
|
183
|
-
"claude-opus-4-6": 0.
|
|
184
|
-
"claude-opus-4-7": 0.
|
|
184
|
+
"claude-opus-4-6": 0.025,
|
|
185
|
+
"claude-opus-4-7": 0.025,
|
|
186
|
+
"claude-opus-4-8": 0.025,
|
|
185
187
|
"claude-sonnet-4-5": 0.015,
|
|
186
188
|
"claude-sonnet-4-6": 0.015,
|
|
187
189
|
"deepseek/deepseek-v4-flash": 0.00028,
|
|
@@ -217,16 +219,16 @@ prices='{
|
|
|
217
219
|
"mistralai/mistral-small-2603": 0.0006,
|
|
218
220
|
"moonshotai/kimi-k2.5": 0.003,
|
|
219
221
|
"moonshotai/kimi-k2.6": 0.004,
|
|
220
|
-
"qwen/qwen3-235b-a22b-2507": 0.
|
|
222
|
+
"qwen/qwen3-235b-a22b-2507": 0.0009064,
|
|
221
223
|
"qwen/qwen3-30b-a3b-instruct-2507": 0.0006,
|
|
222
224
|
"qwen/qwen3-coder": 0.0027,
|
|
223
225
|
"qwen/qwen3-coder-next": 0.0012,
|
|
224
226
|
"qwen/qwen3-next-80b-a3b-instruct": 0.0012,
|
|
225
227
|
"qwen/qwen3.5-flash-02-23": 0.00015,
|
|
226
228
|
"qwen/qwen3.6-35b-a3b": 0.00095,
|
|
227
|
-
"xiaomi/mimo-v2.5": 0.002,
|
|
228
229
|
"xiaomi/mimo-v2.5-pro": 0.003,
|
|
229
|
-
"z-ai/glm-5": 0.00208
|
|
230
|
+
"z-ai/glm-5": 0.00208,
|
|
231
|
+
"z-ai/glm-5.1": 0.0035
|
|
230
232
|
}
|
|
231
233
|
}'
|
|
232
234
|
# END_GENERATED_PRICES
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Route Claude Code directly to Anthropic again (turn the Weave Router off)
|
|
3
|
+
allowed-tools: Bash(npx:*)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Turn the Weave Router **off** for Claude Code by running:
|
|
7
|
+
|
|
8
|
+
`npx @workweave/router off --claude{{SCOPE}}`
|
|
9
|
+
|
|
10
|
+
Then tell me to fully quit and reopen Claude Code — it only reads the router setting at startup, so the change won't apply to the current session until it restarts.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Route Claude Code through the Weave Router again (turn it back on)
|
|
3
|
+
allowed-tools: Bash(npx:*)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Turn the Weave Router **on** for Claude Code by running:
|
|
7
|
+
|
|
8
|
+
`npx @workweave/router on --claude{{SCOPE}}`
|
|
9
|
+
|
|
10
|
+
Then tell me to fully quit and reopen Claude Code so the change takes effect — it only reads the router setting at startup.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show whether Claude Code is routing through the Weave Router or directly to Anthropic
|
|
3
|
+
allowed-tools: Bash(npx:*)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Report whether Claude Code is currently using the Weave Router by running:
|
|
7
|
+
|
|
8
|
+
`npx @workweave/router status --claude{{SCOPE}}`
|
|
9
|
+
|
|
10
|
+
Then summarize the result for me in one line.
|
package/install.sh
CHANGED
|
@@ -37,6 +37,18 @@
|
|
|
37
37
|
# npx @workweave/router --non-interactive # require WEAVE_ROUTER_KEY env var (defaults target to claude)
|
|
38
38
|
# npx @workweave/router --quiet # suppress banner, ping check, and trailing tips
|
|
39
39
|
# npx @workweave/router --uninstall # remove a previous install (delegates to uninstall.sh)
|
|
40
|
+
#
|
|
41
|
+
# Toggle an existing install on/off without losing the router config (so
|
|
42
|
+
# switching back is instant). These never prompt for a key and require an
|
|
43
|
+
# explicit client (--claude / --codex / --opencode); they only flip config
|
|
44
|
+
# that install.sh already wrote:
|
|
45
|
+
# npx @workweave/router off --claude # route directly to Anthropic again (Claude Code)
|
|
46
|
+
# npx @workweave/router on --codex # route through the Weave Router again (Codex)
|
|
47
|
+
# npx @workweave/router status --opencode # report whether opencode is on the router or direct
|
|
48
|
+
# Claude Code reads env at launch, so an off/on takes effect on the next
|
|
49
|
+
# `claude` start; Codex and opencode re-read config every invocation.
|
|
50
|
+
# Cursor's base URL lives in its own settings UI (no file we own), so there's
|
|
51
|
+
# nothing to toggle here — flip "Override OpenAI Base URL" in Cursor settings.
|
|
40
52
|
|
|
41
53
|
set -euo pipefail
|
|
42
54
|
|
|
@@ -65,6 +77,11 @@ router_key_header="X-Weave-Router-Key"
|
|
|
65
77
|
target="claude"
|
|
66
78
|
target_explicit="false"
|
|
67
79
|
|
|
80
|
+
# Operation mode. "install" (default) writes/refreshes config. "off"/"on"/
|
|
81
|
+
# "status" toggle or report an existing install without touching the router
|
|
82
|
+
# key/identity — see the toggle_* helpers and the dispatch block below.
|
|
83
|
+
mode="install"
|
|
84
|
+
|
|
68
85
|
# ---------- helpers ----------
|
|
69
86
|
|
|
70
87
|
# Detect whether stdout is a real terminal that grokks ANSI escapes. Pipes,
|
|
@@ -98,6 +115,29 @@ info() { printf "%s==>%s %s\n" "$C_CYAN" "$C_RESET" "$*"; }
|
|
|
98
115
|
ok() { printf "%s✓%s %s\n" "$C_GREEN" "$C_RESET" "$*"; }
|
|
99
116
|
skip() { printf "%s⊙%s %s%s%s\n" "$C_DIM" "$C_RESET" "$C_DIM" "$*" "$C_RESET"; }
|
|
100
117
|
|
|
118
|
+
# uninstall_cmd echoes the npx one-liner that reverses the current install,
|
|
119
|
+
# matching the target (--codex/--opencode), scope, and --dir so the hint we
|
|
120
|
+
# print after a successful install is copy-paste-correct. Kept in sync with
|
|
121
|
+
# uninstall.sh's flag surface.
|
|
122
|
+
uninstall_cmd() {
|
|
123
|
+
local cmd="npx -y @workweave/router --uninstall"
|
|
124
|
+
case "$target" in
|
|
125
|
+
codex) cmd="$cmd --codex" ;;
|
|
126
|
+
opencode) cmd="$cmd --opencode" ;;
|
|
127
|
+
esac
|
|
128
|
+
[ "$scope" = "project" ] && cmd="$cmd --scope project"
|
|
129
|
+
[ -n "$install_dir" ] && cmd="$cmd --dir $(printf '%q' "$install_dir")"
|
|
130
|
+
printf '%s' "$cmd"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# print_uninstall_hint prints the reverse command on its own labeled line so
|
|
134
|
+
# every successful install ends by telling the user exactly how to undo it.
|
|
135
|
+
print_uninstall_hint() {
|
|
136
|
+
[ "$quiet" = "true" ] && return 0
|
|
137
|
+
printf "%sTo uninstall:%s %s%s%s\n" \
|
|
138
|
+
"$C_BOLD" "$C_RESET" "$C_CYAN" "$(uninstall_cmd)" "$C_RESET"
|
|
139
|
+
}
|
|
140
|
+
|
|
101
141
|
# ---------- banner ----------
|
|
102
142
|
#
|
|
103
143
|
# Print the WEAVE wordmark in brand orange. Skipped under --quiet or when
|
|
@@ -537,6 +577,7 @@ write_opencode_config() {
|
|
|
537
577
|
name: "Weave Router",
|
|
538
578
|
options: { apiKey: $key, baseURL: $url, headers: $headers },
|
|
539
579
|
models: {
|
|
580
|
+
"claude-opus-4-8": { name: "Claude Opus 4.8 (via Weave Router)" },
|
|
540
581
|
"claude-opus-4-7": { name: "Claude Opus 4.7 (via Weave Router)" },
|
|
541
582
|
"claude-sonnet-4-6": { name: "Claude Sonnet 4.6 (via Weave Router)" },
|
|
542
583
|
"claude-haiku-4-5": { name: "Claude Haiku 4.5 (via Weave Router)" }
|
|
@@ -681,6 +722,11 @@ while [ $# -gt 0 ]; do
|
|
|
681
722
|
# on the default.
|
|
682
723
|
target="claude"; target_explicit="true"; shift
|
|
683
724
|
;;
|
|
725
|
+
off|--off|on|--on|status|--status)
|
|
726
|
+
# Toggle/report verbs. Bare (off) or dashed (--off) both accepted; the
|
|
727
|
+
# npm wrapper forwards argv verbatim so either form reaches us.
|
|
728
|
+
mode="${1#--}"; shift
|
|
729
|
+
;;
|
|
684
730
|
-h|--help)
|
|
685
731
|
usage 0
|
|
686
732
|
;;
|
|
@@ -690,6 +736,17 @@ while [ $# -gt 0 ]; do
|
|
|
690
736
|
esac
|
|
691
737
|
done
|
|
692
738
|
|
|
739
|
+
# Toggle verbs only flip config install.sh already wrote: no key, no identity,
|
|
740
|
+
# no prompts. Require an explicit client so we never guess which config to
|
|
741
|
+
# touch, and suppress every interactive prompt downstream.
|
|
742
|
+
if [ "$mode" != "install" ]; then
|
|
743
|
+
non_interactive="true"
|
|
744
|
+
if [ "$target_explicit" != "true" ]; then
|
|
745
|
+
err "'$mode' requires an explicit client: --claude, --codex, or --opencode"
|
|
746
|
+
exit 2
|
|
747
|
+
fi
|
|
748
|
+
fi
|
|
749
|
+
|
|
693
750
|
if [ -z "$base_url" ]; then
|
|
694
751
|
base_url="$DEFAULT_BASE_URL"
|
|
695
752
|
fi
|
|
@@ -723,7 +780,8 @@ fi
|
|
|
723
780
|
# users see when `make full-setup` hands off to install.sh is the wordmark,
|
|
724
781
|
# not a bare "Install scope:" line. Target prompt above already finalized
|
|
725
782
|
# $target, so the banner's per-target label reflects the user's choice.
|
|
726
|
-
|
|
783
|
+
# Toggle verbs stay terse — skip the banner so `status` prints one clean line.
|
|
784
|
+
[ "$mode" = "install" ] && print_banner
|
|
727
785
|
|
|
728
786
|
# ---------- interactive scope prompt ----------
|
|
729
787
|
|
|
@@ -791,15 +849,22 @@ fi
|
|
|
791
849
|
|
|
792
850
|
# ---------- pre-flight ----------
|
|
793
851
|
|
|
794
|
-
[ "$
|
|
852
|
+
if [ "$mode" = "install" ]; then
|
|
853
|
+
[ "$quiet" = "true" ] || info "scope=${C_BOLD}${scope}${C_RESET} target=${C_BOLD}${target}${C_RESET} base_url=${C_BOLD}${base_url}${C_RESET}"
|
|
854
|
+
else
|
|
855
|
+
[ "$quiet" = "true" ] || info "mode=${C_BOLD}${mode}${C_RESET} scope=${C_BOLD}${scope}${C_RESET} target=${C_BOLD}${target}${C_RESET}"
|
|
856
|
+
fi
|
|
795
857
|
|
|
796
858
|
# Codex install only writes a TOML file (managed via awk) so jq isn't needed.
|
|
797
859
|
# Claude Code's settings.json and opencode's opencode.json patching both use
|
|
798
|
-
# jq to deep-merge / structurally rewrite JSON.
|
|
860
|
+
# jq to deep-merge / structurally rewrite JSON. Toggling those clients reads
|
|
861
|
+
# and rewrites the same JSON, so jq is required there too.
|
|
799
862
|
if [ "$target" = "claude" ] || [ "$target" = "opencode" ]; then
|
|
800
863
|
require_cmd jq "macOS: 'brew install jq' · Debian/Ubuntu: 'sudo apt install jq'"
|
|
801
864
|
fi
|
|
802
|
-
|
|
865
|
+
# curl is only used by the install path's health/validate probes; toggles never
|
|
866
|
+
# hit the network.
|
|
867
|
+
[ "$mode" = "install" ] && require_cmd curl "macOS/Linux: usually preinstalled — check your package manager"
|
|
803
868
|
|
|
804
869
|
case "$target" in
|
|
805
870
|
claude)
|
|
@@ -935,6 +1000,348 @@ else
|
|
|
935
1000
|
mkdir -p "$opencode_dir"
|
|
936
1001
|
fi
|
|
937
1002
|
|
|
1003
|
+
# ---------- off / on / status (toggle an existing install) ----------
|
|
1004
|
+
#
|
|
1005
|
+
# Flip a client between the Weave Router and talking to its provider directly,
|
|
1006
|
+
# WITHOUT discarding the router config — so switching back is one command. We
|
|
1007
|
+
# only run this for the `off`/`on`/`status` verbs; `install` falls straight
|
|
1008
|
+
# through to the write path below. An explicit client was already required
|
|
1009
|
+
# during arg parsing, so exactly one of the toggle_* helpers fires.
|
|
1010
|
+
#
|
|
1011
|
+
# Per-client "off" mechanics (each leaves the router config in place so "on"
|
|
1012
|
+
# restores it byte-for-byte):
|
|
1013
|
+
# Claude Code — moves ANTHROPIC_BASE_URL + the key header out of settings
|
|
1014
|
+
# into a parked sidecar; CC falls back to its own login.
|
|
1015
|
+
# Codex — comments the `model_provider = "weave"` line inside the
|
|
1016
|
+
# managed block; the [model_providers.weave] section stays.
|
|
1017
|
+
# opencode — parks the top-level `model` and removes it; opencode reverts
|
|
1018
|
+
# to its own default. The provider.weave block stays.
|
|
1019
|
+
# Claude Code reads env at launch, so its toggle lands on the next `claude`
|
|
1020
|
+
# start; Codex/opencode re-read config every run.
|
|
1021
|
+
|
|
1022
|
+
# router_shaped_url returns 0 when a base URL points at the router — i.e. it's
|
|
1023
|
+
# neither empty nor Anthropic's own endpoint (what "off" falls back to).
|
|
1024
|
+
router_shaped_url() {
|
|
1025
|
+
case "$1" in
|
|
1026
|
+
""|https://api.anthropic.com*|http://api.anthropic.com*) return 1 ;;
|
|
1027
|
+
*) return 0 ;;
|
|
1028
|
+
esac
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
# json_get prints a jq scalar from a file, or empty when the file/key is
|
|
1032
|
+
# absent. Never trips set -e.
|
|
1033
|
+
json_get() {
|
|
1034
|
+
[ -f "$1" ] || return 0
|
|
1035
|
+
jq -r "${2} // empty" "$1" 2>/dev/null || true
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
# claude_key_present returns 0 when the given settings file's
|
|
1039
|
+
# env.ANTHROPIC_CUSTOM_HEADERS carries the router key header. "On" is only valid
|
|
1040
|
+
# when this is true: in project scope the committed settings.json holds the
|
|
1041
|
+
# router URL but the key header lives only in the per-teammate settings.local.json
|
|
1042
|
+
# (or the parked sidecar), so a router URL alone doesn't mean requests can
|
|
1043
|
+
# authenticate.
|
|
1044
|
+
claude_key_present() {
|
|
1045
|
+
case "$(json_get "$1" '.env.ANTHROPIC_CUSTOM_HEADERS')" in
|
|
1046
|
+
*X-Weave-Router-Key*) return 0 ;;
|
|
1047
|
+
*) return 1 ;;
|
|
1048
|
+
esac
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
# gitignore_add appends an entry to the repo .gitignore in project scope so a
|
|
1052
|
+
# parked sidecar (which may carry the router key header) never gets committed.
|
|
1053
|
+
# No-op for user scope and --dir, matching how install handles its own ignores.
|
|
1054
|
+
gitignore_add() {
|
|
1055
|
+
[ "$scope" = "project" ] && [ -z "$install_dir" ] && [ -n "${git_root:-}" ] || return 0
|
|
1056
|
+
local gi="$git_root/.gitignore" entry="$1"
|
|
1057
|
+
refuse_if_symlink "$gi"
|
|
1058
|
+
if [ ! -f "$gi" ] || ! grep -qxF "$entry" "$gi"; then
|
|
1059
|
+
printf '%s\n' "$entry" >>"$gi"
|
|
1060
|
+
fi
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
toggle_claude() {
|
|
1064
|
+
local parked="$settings_dir/.weave-parked.json"
|
|
1065
|
+
local proj="false" active committed_base local_base parked_env merged
|
|
1066
|
+
if [ "$scope" = "project" ] && [ -z "$install_dir" ]; then
|
|
1067
|
+
proj="true"
|
|
1068
|
+
active="$local_settings_file"
|
|
1069
|
+
else
|
|
1070
|
+
active="$settings_file"
|
|
1071
|
+
fi
|
|
1072
|
+
# Symlink containment for the parked sidecar: project/--dir paths come from a
|
|
1073
|
+
# possibly-hostile repo, and `off` writes $parked via shell redirection (which
|
|
1074
|
+
# follows symlinks). A repo could pre-place it as a symlink to clobber an
|
|
1075
|
+
# arbitrary file or siphon the router-key-bearing parked data. The config
|
|
1076
|
+
# files themselves are already guarded during path resolution; this covers the
|
|
1077
|
+
# sidecar. User scope ($HOME) is trusted, matching the installer.
|
|
1078
|
+
if [ "$scope" = "project" ] || [ -n "$install_dir" ]; then
|
|
1079
|
+
refuse_if_symlink "$parked"
|
|
1080
|
+
fi
|
|
1081
|
+
local parked_present="false"
|
|
1082
|
+
if [ -f "$parked" ]; then parked_present="true"; fi
|
|
1083
|
+
committed_base="$(json_get "$settings_file" '.env.ANTHROPIC_BASE_URL')"
|
|
1084
|
+
|
|
1085
|
+
case "$mode" in
|
|
1086
|
+
status)
|
|
1087
|
+
local on_hint="on --claude"
|
|
1088
|
+
[ "$proj" = "true" ] && on_hint="on --claude --scope project"
|
|
1089
|
+
if [ "$parked_present" = "true" ]; then
|
|
1090
|
+
ok "Claude Code: ${C_BOLD}off${C_RESET} — routing directly to Anthropic. Run '$on_hint' to re-enable."
|
|
1091
|
+
elif [ "$proj" = "true" ]; then
|
|
1092
|
+
local_base="$(json_get "$local_settings_file" '.env.ANTHROPIC_BASE_URL')"
|
|
1093
|
+
if [ -n "$local_base" ] && ! router_shaped_url "$local_base"; then
|
|
1094
|
+
ok "Claude Code (project): ${C_BOLD}off${C_RESET} — routing directly to Anthropic. Run '$on_hint' to re-enable."
|
|
1095
|
+
elif router_shaped_url "$committed_base"; then
|
|
1096
|
+
# Router URL is committed, but it only authenticates if this teammate's
|
|
1097
|
+
# settings.local.json carries the key header. A fresh clone (shared
|
|
1098
|
+
# settings.json, no personal local file) has the URL but no key.
|
|
1099
|
+
if claude_key_present "$local_settings_file"; then
|
|
1100
|
+
ok "Claude Code (project): ${C_BOLD}on${C_RESET} — routing through $committed_base."
|
|
1101
|
+
else
|
|
1102
|
+
warn "Claude Code (project): router URL is set but your personal router key is missing (no settings.local.json) — requests won't authenticate. Run the installer to add your key."
|
|
1103
|
+
fi
|
|
1104
|
+
else
|
|
1105
|
+
info "Claude Code (project): not configured for the router. Run the installer first."
|
|
1106
|
+
fi
|
|
1107
|
+
elif router_shaped_url "$committed_base"; then
|
|
1108
|
+
if claude_key_present "$settings_file"; then
|
|
1109
|
+
ok "Claude Code: ${C_BOLD}on${C_RESET} — routing through $committed_base."
|
|
1110
|
+
else
|
|
1111
|
+
warn "Claude Code: router URL is set but the router key header is missing — requests won't authenticate. Run the installer to restore it."
|
|
1112
|
+
fi
|
|
1113
|
+
else
|
|
1114
|
+
info "Claude Code: not configured for the router. Run the installer first."
|
|
1115
|
+
fi
|
|
1116
|
+
;;
|
|
1117
|
+
off)
|
|
1118
|
+
if [ "$parked_present" = "true" ]; then
|
|
1119
|
+
ok "Claude Code is already off — nothing to do."
|
|
1120
|
+
return 0
|
|
1121
|
+
fi
|
|
1122
|
+
if ! router_shaped_url "$committed_base"; then
|
|
1123
|
+
info "Claude Code isn't configured for the router. Run the installer first."
|
|
1124
|
+
return 0
|
|
1125
|
+
fi
|
|
1126
|
+
if [ "$proj" = "true" ]; then
|
|
1127
|
+
# Park the whole local env (carries the key header), then override the
|
|
1128
|
+
# base URL to Anthropic in the local file only — committed settings.json
|
|
1129
|
+
# is never touched, so this stays out of `git diff`.
|
|
1130
|
+
if [ -f "$local_settings_file" ]; then
|
|
1131
|
+
jq '{env: (.env // {})}' "$local_settings_file" >"$parked"
|
|
1132
|
+
merged="$(jq '.env = ((.env // {} | del(.ANTHROPIC_CUSTOM_HEADERS)) + {ANTHROPIC_BASE_URL: "https://api.anthropic.com"})' "$local_settings_file")"
|
|
1133
|
+
else
|
|
1134
|
+
printf '{"env":{}}\n' >"$parked"
|
|
1135
|
+
merged='{"env":{"ANTHROPIC_BASE_URL":"https://api.anthropic.com"}}'
|
|
1136
|
+
fi
|
|
1137
|
+
printf '%s\n' "$merged" >"$local_settings_file"
|
|
1138
|
+
chmod 600 "$local_settings_file" "$parked"
|
|
1139
|
+
gitignore_add ".claude/.weave-parked.json"
|
|
1140
|
+
else
|
|
1141
|
+
# Park just the router-owned env keys, then strip them so Claude Code
|
|
1142
|
+
# falls back to its own Anthropic login.
|
|
1143
|
+
jq '{env: ((.env // {}) | {ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS} | with_entries(select(.value != null)))}' "$settings_file" >"$parked"
|
|
1144
|
+
chmod 600 "$parked"
|
|
1145
|
+
merged="$(jq '(.env // {}) |= del(.ANTHROPIC_BASE_URL, .ANTHROPIC_CUSTOM_HEADERS)
|
|
1146
|
+
| (if (.env // {} | length) == 0 then del(.env) else . end)' "$settings_file")"
|
|
1147
|
+
printf '%s\n' "$merged" >"$settings_file"
|
|
1148
|
+
fi
|
|
1149
|
+
ok "Claude Code is now ${C_BOLD}off${C_RESET} (direct to Anthropic). Restart Claude Code for it to take effect."
|
|
1150
|
+
;;
|
|
1151
|
+
on)
|
|
1152
|
+
if [ "$parked_present" != "true" ]; then
|
|
1153
|
+
# Project scope can still carry a direct-override in settings.local.json
|
|
1154
|
+
# even with no sidecar (e.g. the parked file was deleted by hand). That
|
|
1155
|
+
# override wins over the committed router URL, so traffic is really
|
|
1156
|
+
# going direct — drop it so "on" matches what "status" reports, instead
|
|
1157
|
+
# of falsely claiming we're already on.
|
|
1158
|
+
if [ "$proj" = "true" ]; then
|
|
1159
|
+
local_base="$(json_get "$local_settings_file" '.env.ANTHROPIC_BASE_URL')"
|
|
1160
|
+
if [ -n "$local_base" ] && ! router_shaped_url "$local_base"; then
|
|
1161
|
+
# We're off, but the parked sidecar is gone. The router key header
|
|
1162
|
+
# lives only in the local file / sidecar in project scope — never in
|
|
1163
|
+
# committed settings.json — so we can only re-enable cleanly if the
|
|
1164
|
+
# header survived in the local file. If it didn't, clearing the
|
|
1165
|
+
# override would point Claude Code at the router with no auth
|
|
1166
|
+
# (401s); leave the working direct setup in place and tell the user
|
|
1167
|
+
# to reinstall instead of faking success.
|
|
1168
|
+
if claude_key_present "$local_settings_file"; then
|
|
1169
|
+
merged="$(jq '(.env // {}) |= del(.ANTHROPIC_BASE_URL)
|
|
1170
|
+
| (if (.env // {} | length) == 0 then del(.env) else . end)' "$local_settings_file")"
|
|
1171
|
+
printf '%s\n' "$merged" >"$local_settings_file"
|
|
1172
|
+
chmod 600 "$local_settings_file"
|
|
1173
|
+
ok "Claude Code is now ${C_BOLD}on${C_RESET} (routing through the Weave Router). Restart Claude Code for it to take effect."
|
|
1174
|
+
else
|
|
1175
|
+
warn "Claude Code is off and the parked router key is missing (its sidecar was deleted). Re-run the installer to restore the router key — leaving the current direct-to-Anthropic setup in place so requests don't fail auth."
|
|
1176
|
+
fi
|
|
1177
|
+
return 0
|
|
1178
|
+
fi
|
|
1179
|
+
fi
|
|
1180
|
+
# No direct override (or user scope). "On" requires both the router URL
|
|
1181
|
+
# and the key header — a committed router URL with no local key (e.g. a
|
|
1182
|
+
# fresh clone) can't authenticate, so don't claim it's already on.
|
|
1183
|
+
if ! router_shaped_url "$committed_base"; then
|
|
1184
|
+
warn "No parked router config found. Run the installer to set up Claude Code."
|
|
1185
|
+
elif claude_key_present "$active"; then
|
|
1186
|
+
ok "Claude Code is already on — nothing to do."
|
|
1187
|
+
else
|
|
1188
|
+
# $active is settings.local.json in project scope, settings.json for
|
|
1189
|
+
# user/--dir — name the right file so the hint isn't misleading.
|
|
1190
|
+
warn "Router URL is set but the router key is missing — run the installer to add your key (written to $(basename "$active"))."
|
|
1191
|
+
fi
|
|
1192
|
+
return 0
|
|
1193
|
+
fi
|
|
1194
|
+
# Sidecar present: restore it — but only if the result actually carries
|
|
1195
|
+
# the router key. An off that ran with an empty/absent settings.local.json
|
|
1196
|
+
# parks {"env":{}}; blindly restoring that would drop the direct override
|
|
1197
|
+
# and leave the committed router URL unauthenticated while printing
|
|
1198
|
+
# success. Refuse that and tell the user to reinstall.
|
|
1199
|
+
parked_env="$(jq -c '.env // {}' "$parked")"
|
|
1200
|
+
parked_has_key="false"
|
|
1201
|
+
if printf '%s' "$parked_env" | jq -e '(.ANTHROPIC_CUSTOM_HEADERS // "") | test("X-Weave-Router-Key")' >/dev/null 2>&1; then
|
|
1202
|
+
parked_has_key="true"
|
|
1203
|
+
fi
|
|
1204
|
+
if [ "$parked_has_key" != "true" ] && ! claude_key_present "$active"; then
|
|
1205
|
+
warn "Can't re-enable: the parked config has no router key (it was created without one). Re-run the installer to set up your router key — leaving the current direct-to-Anthropic setup in place."
|
|
1206
|
+
return 0
|
|
1207
|
+
fi
|
|
1208
|
+
merged="$(jq --argjson p "$parked_env" '.env = (((.env // {}) | del(.ANTHROPIC_BASE_URL)) + $p)' "$active")"
|
|
1209
|
+
printf '%s\n' "$merged" >"$active"
|
|
1210
|
+
[ "$proj" = "true" ] && chmod 600 "$active"
|
|
1211
|
+
rm -f "$parked"
|
|
1212
|
+
ok "Claude Code is now ${C_BOLD}on${C_RESET} (routing through the Weave Router). Restart Claude Code for it to take effect."
|
|
1213
|
+
;;
|
|
1214
|
+
esac
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
toggle_codex() {
|
|
1218
|
+
local f="$codex_config_file" state="absent" tmp
|
|
1219
|
+
if [ -f "$f" ]; then
|
|
1220
|
+
state="$(awk -v b="$WEAVE_CODEX_BEGIN_MARKER" -v e="$WEAVE_CODEX_END_MARKER" '
|
|
1221
|
+
$0==b{inblk=1; next}
|
|
1222
|
+
$0==e{inblk=0; next}
|
|
1223
|
+
inblk && /^[[:space:]]*model_provider[[:space:]]*=[[:space:]]*"weave"/ {st="on"}
|
|
1224
|
+
inblk && /^[[:space:]]*#[[:space:]]*model_provider[[:space:]]*=[[:space:]]*"weave"/ {if(st=="")st="off"}
|
|
1225
|
+
END{print (st==""?"absent":st)}
|
|
1226
|
+
' "$f")"
|
|
1227
|
+
fi
|
|
1228
|
+
|
|
1229
|
+
case "$mode" in
|
|
1230
|
+
status)
|
|
1231
|
+
case "$state" in
|
|
1232
|
+
on) ok "Codex: ${C_BOLD}on${C_RESET} — routing through the Weave Router." ;;
|
|
1233
|
+
off) ok "Codex: ${C_BOLD}off${C_RESET} — using Codex's default provider. Run 'on --codex' to re-enable." ;;
|
|
1234
|
+
*) info "Codex: not configured for the router. Run the installer first." ;;
|
|
1235
|
+
esac
|
|
1236
|
+
;;
|
|
1237
|
+
off)
|
|
1238
|
+
if [ "$state" = "absent" ]; then info "Codex isn't configured for the router. Run the installer first."; return 0; fi
|
|
1239
|
+
if [ "$state" = "off" ]; then ok "Codex is already off — nothing to do."; return 0; fi
|
|
1240
|
+
tmp="$(mktemp -t weave-codex-toggle.XXXXXX)"
|
|
1241
|
+
awk -v b="$WEAVE_CODEX_BEGIN_MARKER" -v e="$WEAVE_CODEX_END_MARKER" '
|
|
1242
|
+
$0==b{inblk=1; print; next}
|
|
1243
|
+
$0==e{inblk=0; print; next}
|
|
1244
|
+
inblk && /^[[:space:]]*model_provider[[:space:]]*=[[:space:]]*"weave"[[:space:]]*$/ {
|
|
1245
|
+
print "# " $0 " # weave-router: off (run on to re-enable)"; next
|
|
1246
|
+
}
|
|
1247
|
+
{print}
|
|
1248
|
+
' "$f" >"$tmp" && mv "$tmp" "$f"
|
|
1249
|
+
chmod 600 "$f"
|
|
1250
|
+
ok "Codex is now ${C_BOLD}off${C_RESET} (default provider). Takes effect on your next 'codex' run."
|
|
1251
|
+
;;
|
|
1252
|
+
on)
|
|
1253
|
+
if [ "$state" = "absent" ]; then warn "No managed Weave block in $f. Run the installer to set up Codex."; return 0; fi
|
|
1254
|
+
if [ "$state" = "on" ]; then ok "Codex is already on — nothing to do."; return 0; fi
|
|
1255
|
+
tmp="$(mktemp -t weave-codex-toggle.XXXXXX)"
|
|
1256
|
+
awk -v b="$WEAVE_CODEX_BEGIN_MARKER" -v e="$WEAVE_CODEX_END_MARKER" '
|
|
1257
|
+
$0==b{inblk=1; print; next}
|
|
1258
|
+
$0==e{inblk=0; print; next}
|
|
1259
|
+
inblk && /^[[:space:]]*#[[:space:]]*model_provider[[:space:]]*=[[:space:]]*"weave"/ {
|
|
1260
|
+
print "model_provider = \"weave\""; next
|
|
1261
|
+
}
|
|
1262
|
+
{print}
|
|
1263
|
+
' "$f" >"$tmp" && mv "$tmp" "$f"
|
|
1264
|
+
chmod 600 "$f"
|
|
1265
|
+
ok "Codex is now ${C_BOLD}on${C_RESET} (routing through the Weave Router). Takes effect on your next 'codex' run."
|
|
1266
|
+
;;
|
|
1267
|
+
esac
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
toggle_opencode() {
|
|
1271
|
+
local f="$opencode_config_file" parked="$opencode_dir/.weave-parked.json"
|
|
1272
|
+
local model="" has_weave="false" parked_present="false" on="false" restore_model merged
|
|
1273
|
+
# Symlink containment for the parked sidecar — `off` writes it via shell
|
|
1274
|
+
# redirection; a hostile project repo could pre-place it as a symlink. The
|
|
1275
|
+
# opencode.json itself is already guarded during path resolution.
|
|
1276
|
+
if [ "$scope" = "project" ] || [ -n "$install_dir" ]; then
|
|
1277
|
+
refuse_if_symlink "$parked"
|
|
1278
|
+
fi
|
|
1279
|
+
if [ -f "$parked" ]; then parked_present="true"; fi
|
|
1280
|
+
if [ -f "$f" ]; then
|
|
1281
|
+
model="$(jq -r '.model // empty' "$f" 2>/dev/null || true)"
|
|
1282
|
+
if [ "$(jq -r '((.provider // {}) | has("weave"))' "$f" 2>/dev/null || true)" = "true" ]; then has_weave="true"; fi
|
|
1283
|
+
fi
|
|
1284
|
+
case "$model" in weave/*) on="true" ;; esac
|
|
1285
|
+
|
|
1286
|
+
case "$mode" in
|
|
1287
|
+
status)
|
|
1288
|
+
if [ "$on" = "true" ]; then
|
|
1289
|
+
ok "opencode: ${C_BOLD}on${C_RESET} — default model is $model (via the Weave Router)."
|
|
1290
|
+
elif [ "$has_weave" = "true" ] || [ "$parked_present" = "true" ]; then
|
|
1291
|
+
ok "opencode: ${C_BOLD}off${C_RESET} — not using the Weave Router model. Run 'on --opencode' to re-enable."
|
|
1292
|
+
else
|
|
1293
|
+
info "opencode: not configured for the router. Run the installer first."
|
|
1294
|
+
fi
|
|
1295
|
+
;;
|
|
1296
|
+
off)
|
|
1297
|
+
if [ "$on" != "true" ]; then
|
|
1298
|
+
if [ "$has_weave" = "true" ]; then ok "opencode is already off — nothing to do."; else info "opencode isn't configured for the router. Run the installer first."; fi
|
|
1299
|
+
return 0
|
|
1300
|
+
fi
|
|
1301
|
+
jq '{model: .model}' "$f" >"$parked"
|
|
1302
|
+
chmod 600 "$parked"
|
|
1303
|
+
merged="$(jq 'del(.model)' "$f")"
|
|
1304
|
+
printf '%s\n' "$merged" >"$f"
|
|
1305
|
+
chmod 600 "$f"
|
|
1306
|
+
gitignore_add ".weave-parked.json"
|
|
1307
|
+
ok "opencode is now ${C_BOLD}off${C_RESET} — pick a non-Weave model with /models. Takes effect on your next opencode run."
|
|
1308
|
+
;;
|
|
1309
|
+
on)
|
|
1310
|
+
if [ "$on" = "true" ]; then ok "opencode is already on — nothing to do."; return 0; fi
|
|
1311
|
+
restore_model="weave/claude-sonnet-4-6"
|
|
1312
|
+
if [ "$parked_present" = "true" ]; then
|
|
1313
|
+
restore_model="$(jq -r '.model // "weave/claude-sonnet-4-6"' "$parked")"
|
|
1314
|
+
elif [ "$has_weave" != "true" ]; then
|
|
1315
|
+
warn "opencode isn't configured for the router. Run the installer first."; return 0
|
|
1316
|
+
else
|
|
1317
|
+
# No parked model (sidecar deleted by hand). Derive the default from the
|
|
1318
|
+
# installed provider.weave.models block rather than a hardcoded literal
|
|
1319
|
+
# that silently diverges when the installer's default changes — prefer a
|
|
1320
|
+
# sonnet entry, else the first model the installer registered.
|
|
1321
|
+
restore_model="$(jq -r '
|
|
1322
|
+
(.provider.weave.models // {} | keys) as $k
|
|
1323
|
+
| (([$k[] | select(test("sonnet"))] | first) // $k[0] // "claude-sonnet-4-6")
|
|
1324
|
+
| "weave/" + .
|
|
1325
|
+
' "$f" 2>/dev/null || echo "weave/claude-sonnet-4-6")"
|
|
1326
|
+
fi
|
|
1327
|
+
merged="$(jq --arg m "$restore_model" '.model = $m' "$f")"
|
|
1328
|
+
printf '%s\n' "$merged" >"$f"
|
|
1329
|
+
chmod 600 "$f"
|
|
1330
|
+
rm -f "$parked"
|
|
1331
|
+
ok "opencode is now ${C_BOLD}on${C_RESET} (default model $restore_model via the Weave Router). Takes effect on your next opencode run."
|
|
1332
|
+
;;
|
|
1333
|
+
esac
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if [ "$mode" != "install" ]; then
|
|
1337
|
+
case "$target" in
|
|
1338
|
+
claude) toggle_claude ;;
|
|
1339
|
+
codex) toggle_codex ;;
|
|
1340
|
+
opencode) toggle_opencode ;;
|
|
1341
|
+
esac
|
|
1342
|
+
exit 0
|
|
1343
|
+
fi
|
|
1344
|
+
|
|
938
1345
|
# ---------- token handling ----------
|
|
939
1346
|
|
|
940
1347
|
api_key=""
|
|
@@ -1027,16 +1434,44 @@ install_slash_commands() {
|
|
|
1027
1434
|
refuse_if_symlink "$dst_dir"
|
|
1028
1435
|
fi
|
|
1029
1436
|
mkdir -p "$dst_dir"
|
|
1030
|
-
|
|
1437
|
+
|
|
1438
|
+
# force-model/unforce-model are router-intercepted prompt expansions and
|
|
1439
|
+
# apply to every target. The router-off/on/status wrappers shell out to this
|
|
1440
|
+
# installer to flip the *local* config, so they're Claude Code-only and need
|
|
1441
|
+
# the install scope baked into the command (the .md can't discover it at
|
|
1442
|
+
# invocation time). {{SCOPE}} is substituted accordingly.
|
|
1443
|
+
installed="force-model, unforce-model"
|
|
1444
|
+
cmds="force-model unforce-model"
|
|
1445
|
+
if [ "$target" = "claude" ]; then
|
|
1446
|
+
cmds="$cmds router-off router-on router-status"
|
|
1447
|
+
installed="$installed, router-off, router-on, router-status"
|
|
1448
|
+
fi
|
|
1449
|
+
|
|
1450
|
+
# Bake the same scope selector the toggle needs to find this install: --dir
|
|
1451
|
+
# overrides scope (mirrors install/uninstall path resolution), so a sandbox
|
|
1452
|
+
# install gets `--dir <path>` and the slash commands flip that dir rather
|
|
1453
|
+
# than the default user-scope paths. printf %q quotes paths with spaces.
|
|
1454
|
+
local scope_args=""
|
|
1455
|
+
if [ -n "$install_dir" ]; then
|
|
1456
|
+
scope_args=" --dir $(printf '%q' "$install_dir")"
|
|
1457
|
+
elif [ "$scope" = "project" ]; then
|
|
1458
|
+
scope_args=" --scope project"
|
|
1459
|
+
fi
|
|
1460
|
+
|
|
1461
|
+
for cmd in $cmds; do
|
|
1031
1462
|
src="$commands_src_dir/$cmd.md"
|
|
1032
1463
|
dst="$dst_dir/$cmd.md"
|
|
1033
1464
|
[ -f "$src" ] || continue
|
|
1034
1465
|
if [ "$scope" = "project" ] || [ -n "$install_dir" ]; then
|
|
1035
1466
|
refuse_if_symlink "$dst"
|
|
1036
1467
|
fi
|
|
1037
|
-
|
|
1468
|
+
# Substitute the {{SCOPE}} placeholder (only the router-* wrappers carry it;
|
|
1469
|
+
# cp-equivalent for the others since the token is absent).
|
|
1470
|
+
local body; body="$(cat "$src")"
|
|
1471
|
+
body="${body//\{\{SCOPE\}\}/$scope_args}"
|
|
1472
|
+
printf '%s\n' "$body" >"$dst"
|
|
1038
1473
|
done
|
|
1039
|
-
ok "Slash commands written to $dst_dir (
|
|
1474
|
+
ok "Slash commands written to $dst_dir ($installed)"
|
|
1040
1475
|
}
|
|
1041
1476
|
|
|
1042
1477
|
# ---------- codex install path (dispatch + exit before the Claude-only writes) ----------
|
|
@@ -1091,6 +1526,7 @@ if [ "$target" = "codex" ]; then
|
|
|
1091
1526
|
# point CODEX_HOME at the directory we wrote so Codex finds our config.
|
|
1092
1527
|
info "Run Codex with CODEX_HOME=$codex_dir codex so it picks up this config."
|
|
1093
1528
|
fi
|
|
1529
|
+
print_uninstall_hint
|
|
1094
1530
|
exit 0
|
|
1095
1531
|
fi
|
|
1096
1532
|
|
|
@@ -1141,6 +1577,7 @@ if [ "$target" = "opencode" ]; then
|
|
|
1141
1577
|
# has to point opencode at the file explicitly.
|
|
1142
1578
|
info "Run opencode with OPENCODE_CONFIG=$opencode_config_file opencode."
|
|
1143
1579
|
fi
|
|
1580
|
+
print_uninstall_hint
|
|
1144
1581
|
exit 0
|
|
1145
1582
|
fi
|
|
1146
1583
|
|
|
@@ -1279,8 +1716,9 @@ normalize_model() {
|
|
|
1279
1716
|
prices='{
|
|
1280
1717
|
"input": {
|
|
1281
1718
|
"claude-haiku-4-5": 0.0008,
|
|
1282
|
-
"claude-opus-4-6": 0.
|
|
1283
|
-
"claude-opus-4-7": 0.
|
|
1719
|
+
"claude-opus-4-6": 0.005,
|
|
1720
|
+
"claude-opus-4-7": 0.005,
|
|
1721
|
+
"claude-opus-4-8": 0.005,
|
|
1284
1722
|
"claude-sonnet-4-5": 0.003,
|
|
1285
1723
|
"claude-sonnet-4-6": 0.003,
|
|
1286
1724
|
"deepseek/deepseek-v4-flash": 0.00014,
|
|
@@ -1316,21 +1754,22 @@ prices='{
|
|
|
1316
1754
|
"mistralai/mistral-small-2603": 0.0002,
|
|
1317
1755
|
"moonshotai/kimi-k2.5": 0.0006,
|
|
1318
1756
|
"moonshotai/kimi-k2.6": 0.00095,
|
|
1319
|
-
"qwen/qwen3-235b-a22b-2507": 0.
|
|
1757
|
+
"qwen/qwen3-235b-a22b-2507": 0.0002266,
|
|
1320
1758
|
"qwen/qwen3-30b-a3b-instruct-2507": 0.00015,
|
|
1321
1759
|
"qwen/qwen3-coder": 0.0009,
|
|
1322
1760
|
"qwen/qwen3-coder-next": 0.0005,
|
|
1323
1761
|
"qwen/qwen3-next-80b-a3b-instruct": 0.00015,
|
|
1324
1762
|
"qwen/qwen3.5-flash-02-23": 0.00005,
|
|
1325
1763
|
"qwen/qwen3.6-35b-a3b": 0.00015,
|
|
1326
|
-
"xiaomi/mimo-v2.5": 0.0004,
|
|
1327
1764
|
"xiaomi/mimo-v2.5-pro": 0.001,
|
|
1328
|
-
"z-ai/glm-5": 0.0006
|
|
1765
|
+
"z-ai/glm-5": 0.0006,
|
|
1766
|
+
"z-ai/glm-5.1": 0.00105
|
|
1329
1767
|
},
|
|
1330
1768
|
"output": {
|
|
1331
1769
|
"claude-haiku-4-5": 0.004,
|
|
1332
|
-
"claude-opus-4-6": 0.
|
|
1333
|
-
"claude-opus-4-7": 0.
|
|
1770
|
+
"claude-opus-4-6": 0.025,
|
|
1771
|
+
"claude-opus-4-7": 0.025,
|
|
1772
|
+
"claude-opus-4-8": 0.025,
|
|
1334
1773
|
"claude-sonnet-4-5": 0.015,
|
|
1335
1774
|
"claude-sonnet-4-6": 0.015,
|
|
1336
1775
|
"deepseek/deepseek-v4-flash": 0.00028,
|
|
@@ -1366,16 +1805,16 @@ prices='{
|
|
|
1366
1805
|
"mistralai/mistral-small-2603": 0.0006,
|
|
1367
1806
|
"moonshotai/kimi-k2.5": 0.003,
|
|
1368
1807
|
"moonshotai/kimi-k2.6": 0.004,
|
|
1369
|
-
"qwen/qwen3-235b-a22b-2507": 0.
|
|
1808
|
+
"qwen/qwen3-235b-a22b-2507": 0.0009064,
|
|
1370
1809
|
"qwen/qwen3-30b-a3b-instruct-2507": 0.0006,
|
|
1371
1810
|
"qwen/qwen3-coder": 0.0027,
|
|
1372
1811
|
"qwen/qwen3-coder-next": 0.0012,
|
|
1373
1812
|
"qwen/qwen3-next-80b-a3b-instruct": 0.0012,
|
|
1374
1813
|
"qwen/qwen3.5-flash-02-23": 0.00015,
|
|
1375
1814
|
"qwen/qwen3.6-35b-a3b": 0.00095,
|
|
1376
|
-
"xiaomi/mimo-v2.5": 0.002,
|
|
1377
1815
|
"xiaomi/mimo-v2.5-pro": 0.003,
|
|
1378
|
-
"z-ai/glm-5": 0.00208
|
|
1816
|
+
"z-ai/glm-5": 0.00208,
|
|
1817
|
+
"z-ai/glm-5.1": 0.0035
|
|
1379
1818
|
}
|
|
1380
1819
|
}'
|
|
1381
1820
|
# END_GENERATED_PRICES
|
|
@@ -1676,3 +2115,4 @@ fi
|
|
|
1676
2115
|
printf "\n"
|
|
1677
2116
|
printf "%s✓%s %s%sWeave Router installed for Claude Code.%s\n" \
|
|
1678
2117
|
"$C_GREEN" "$C_RESET" "$C_BOLD" "$C_BRAND" "$C_RESET"
|
|
2118
|
+
print_uninstall_hint
|
package/package.json
CHANGED
package/uninstall.sh
CHANGED
|
@@ -173,6 +173,14 @@ if [ "$target" = "opencode" ]; then
|
|
|
173
173
|
info "No opencode config at $opencode_config_file (already uninstalled?)"
|
|
174
174
|
fi
|
|
175
175
|
|
|
176
|
+
# Drop the toggle parked sidecar (holds the parked router model when off).
|
|
177
|
+
opencode_parked="$opencode_dir/.weave-parked.json"
|
|
178
|
+
if [ -f "$opencode_parked" ]; then
|
|
179
|
+
refuse_if_symlink "$opencode_parked"
|
|
180
|
+
rm -f "$opencode_parked"
|
|
181
|
+
ok "Removed $opencode_parked"
|
|
182
|
+
fi
|
|
183
|
+
|
|
176
184
|
if [ -n "$install_dir" ]; then
|
|
177
185
|
ok "Weave Router uninstalled from $install_dir (opencode)."
|
|
178
186
|
else
|
|
@@ -351,9 +359,12 @@ else
|
|
|
351
359
|
fi
|
|
352
360
|
|
|
353
361
|
if [ -n "$local_settings_file" ] && [ -f "$local_settings_file" ]; then
|
|
362
|
+
# ANTHROPIC_BASE_URL only lives here when a project install was toggled off
|
|
363
|
+
# (the off path overrides it to Anthropic in the local file); scrub it too so
|
|
364
|
+
# uninstall fully reverts a toggled-off install.
|
|
354
365
|
cleaned="$(jq '
|
|
355
366
|
if .env then
|
|
356
|
-
.env |= (del(.ANTHROPIC_AUTH_TOKEN, .ANTHROPIC_CUSTOM_HEADERS))
|
|
367
|
+
.env |= (del(.ANTHROPIC_BASE_URL, .ANTHROPIC_AUTH_TOKEN, .ANTHROPIC_CUSTOM_HEADERS))
|
|
357
368
|
| (if (.env | length) == 0 then del(.env) else . end)
|
|
358
369
|
else . end
|
|
359
370
|
| del(.apiKeyHelper)
|
|
@@ -362,6 +373,14 @@ if [ -n "$local_settings_file" ] && [ -f "$local_settings_file" ]; then
|
|
|
362
373
|
ok "Cleaned $local_settings_file"
|
|
363
374
|
fi
|
|
364
375
|
|
|
376
|
+
# Drop the toggle parked sidecar (carries the router key header when off).
|
|
377
|
+
parked_file="$(dirname "$settings_file")/.weave-parked.json"
|
|
378
|
+
if [ -f "$parked_file" ]; then
|
|
379
|
+
refuse_if_symlink "$parked_file"
|
|
380
|
+
rm -f "$parked_file"
|
|
381
|
+
ok "Removed $parked_file"
|
|
382
|
+
fi
|
|
383
|
+
|
|
365
384
|
for f in "$statusline_file"; do
|
|
366
385
|
if [ -f "$f" ]; then
|
|
367
386
|
rm -f "$f"
|
|
@@ -375,7 +394,7 @@ done
|
|
|
375
394
|
commands_dir="$(dirname "$settings_file")/commands"
|
|
376
395
|
if [ -d "$commands_dir" ]; then
|
|
377
396
|
refuse_if_symlink "$commands_dir"
|
|
378
|
-
for cmd in force-model unforce-model; do
|
|
397
|
+
for cmd in force-model unforce-model router-off router-on router-status; do
|
|
379
398
|
cmd_file="$commands_dir/$cmd.md"
|
|
380
399
|
if [ -f "$cmd_file" ]; then
|
|
381
400
|
refuse_if_symlink "$cmd_file"
|