@workweave/router 0.1.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/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@workweave/router",
3
+ "version": "0.1.0",
4
+ "description": "One-command installer that points Claude Code at the Weave Router.",
5
+ "bin": {
6
+ "weave-router": "bin.js"
7
+ },
8
+ "files": [
9
+ "bin.js",
10
+ "install.sh",
11
+ "uninstall.sh",
12
+ "cc-statusline.sh",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "prepack": "node ./scripts/copy-installer.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/workweave/router.git",
24
+ "directory": "install/npm"
25
+ },
26
+ "homepage": "https://weave.ai",
27
+ "license": "Apache-2.0",
28
+ "keywords": [
29
+ "weave",
30
+ "router",
31
+ "claude",
32
+ "claude-code",
33
+ "anthropic",
34
+ "llm-router"
35
+ ]
36
+ }
package/uninstall.sh ADDED
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Weave Router uninstaller for Claude Code.
4
+ #
5
+ # Removes the env vars, statusLine, and local router auth that install.sh added.
6
+ # Leaves the rest of settings.json untouched.
7
+ #
8
+ # Usage:
9
+ # npx @workweave/router --uninstall # user scope
10
+ # npx @workweave/router --uninstall --scope project # run inside the repo
11
+ # npx @workweave/router --uninstall --dir /tmp/test # --dir alone (user scope, .weave/)
12
+ # npx @workweave/router --uninstall --scope project --dir /tmp # --dir + project scope (.claude/)
13
+
14
+ set -euo pipefail
15
+
16
+ scope="user"
17
+ scope_explicit="false"
18
+ install_dir=""
19
+
20
+ err() { printf "\033[31merror:\033[0m %s\n" "$*" >&2; }
21
+ info() { printf "\033[36m==>\033[0m %s\n" "$*"; }
22
+ ok() { printf "\033[32m✓\033[0m %s\n" "$*"; }
23
+
24
+ # Refuse to write/delete through a symlink. Project scope reads paths from the
25
+ # user's git repo; a hostile checkout could ship `.claude/settings.json` (or
26
+ # `.claude/cc-statusline.sh`) as a symlink to e.g. `~/.ssh/authorized_keys`,
27
+ # and the uninstaller's `>` redirect or `rm` would silently follow that link.
28
+ refuse_if_symlink() {
29
+ local target="$1"
30
+ if [ -L "$target" ]; then
31
+ err "$target is a symlink (-> $(readlink "$target")). Refusing to operate on it."
32
+ exit 1
33
+ fi
34
+ }
35
+
36
+ while [ $# -gt 0 ]; do
37
+ case "$1" in
38
+ --scope)
39
+ scope="${2:-}"; shift 2
40
+ [ "$scope" = "user" ] || [ "$scope" = "project" ] || { err "--scope must be 'user' or 'project'"; exit 2; }
41
+ scope_explicit="true"
42
+ ;;
43
+ --dir)
44
+ install_dir="${2:-}"; shift 2
45
+ [ -n "$install_dir" ] || { err "--dir requires a path"; exit 2; }
46
+ ;;
47
+ -h|--help)
48
+ # awk avoids GNU `head -n -<N>` (rejected by BSD head on macOS).
49
+ awk 'NR<2 { next } /^set -euo/ { exit } { sub(/^# ?/, ""); print }' "$0"
50
+ exit 0
51
+ ;;
52
+ *)
53
+ err "unknown flag: $1"; exit 2
54
+ ;;
55
+ esac
56
+ done
57
+
58
+ if ! command -v jq >/dev/null 2>&1; then
59
+ err "jq is required."
60
+ exit 1
61
+ fi
62
+
63
+ # Resolve the base directory. When --dir is given, use it directly.
64
+ if [ -n "$install_dir" ]; then
65
+ install_dir="$(cd "$install_dir" 2>/dev/null && pwd || echo "$install_dir")"
66
+ settings_file="$install_dir/.claude/settings.json"
67
+ local_settings_file=""
68
+ # --dir alone (scope defaults to "user") uses .weave/; --dir --scope project
69
+ # uses .claude/. Match the installer's scope-dependent statusline placement.
70
+ if [ "$scope" = "project" ]; then
71
+ statusline_file="$install_dir/.claude/cc-statusline.sh"
72
+ else
73
+ statusline_file="$install_dir/.weave/cc-statusline.sh"
74
+ fi
75
+ # Symlink containment: --dir paths come from a user-supplied directory that may
76
+ # be hostile. The later `>` redirect on settings_file and `rm -f` on the
77
+ # statusline script would otherwise follow links out of the directory.
78
+ refuse_if_symlink "$install_dir/.claude"
79
+ refuse_if_symlink "$settings_file"
80
+ refuse_if_symlink "$statusline_file"
81
+ elif [ "$scope" = "user" ]; then
82
+ settings_file="$HOME/.claude/settings.json"
83
+ local_settings_file=""
84
+ statusline_file="$HOME/.weave/cc-statusline.sh"
85
+ else
86
+ # Project scope without --dir: mirror install.sh — directory prompt only when
87
+ # scope_explicit is false (interactive install path); explicit --scope project
88
+ # uses the git root of CWD with no prompt.
89
+ project_dir=""
90
+ if [ "$scope_explicit" = "false" ] && [ -r /dev/tty ]; then
91
+ default_project_dir="$(pwd)"
92
+ printf "Project directory to uninstall from [default: %s]: " "$default_project_dir"
93
+ read -r project_dir_choice </dev/tty || project_dir_choice=""
94
+ project_dir="${project_dir_choice:-$default_project_dir}"
95
+ case "$project_dir" in
96
+ "~") project_dir="$HOME" ;;
97
+ "~/"*) project_dir="$HOME/${project_dir#~/}" ;;
98
+ esac
99
+ if [ ! -d "$project_dir" ]; then
100
+ err "directory does not exist: $project_dir"
101
+ exit 1
102
+ fi
103
+ project_dir="$(cd "$project_dir" && pwd)"
104
+ fi
105
+ if [ -n "${project_dir:-}" ]; then
106
+ settings_base="$project_dir"
107
+ else
108
+ if ! git_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
109
+ err "--scope project must be run inside a git repo, or use --dir <path>."
110
+ exit 1
111
+ fi
112
+ settings_base="$git_root"
113
+ fi
114
+ settings_file="$settings_base/.claude/settings.json"
115
+ local_settings_file="$settings_base/.claude/settings.local.json"
116
+ statusline_file="$settings_base/.claude/cc-statusline.sh"
117
+ # Symlink containment: paths come from a git repo or user-supplied directory
118
+ # that may be hostile. The later `>` redirect on settings_file and `rm -f` on
119
+ # the scripts would otherwise follow links out of the repo.
120
+ refuse_if_symlink "$settings_base/.claude"
121
+ refuse_if_symlink "$settings_file"
122
+ refuse_if_symlink "$local_settings_file"
123
+ refuse_if_symlink "$statusline_file"
124
+ fi
125
+
126
+ if [ -f "$settings_file" ]; then
127
+ # Only remove keys we actually installed: scrub our two env vars, and only
128
+ # delete `statusLine` / `apiKeyHelper` when they point at scripts this
129
+ # installer used in older versions. Otherwise an unrelated user-configured
130
+ # statusLine or apiKeyHelper would be silently clobbered.
131
+ cleaned="$(jq '
132
+ if .env then
133
+ .env |= (del(.ANTHROPIC_BASE_URL, .ANTHROPIC_AUTH_TOKEN, .ANTHROPIC_CUSTOM_HEADERS))
134
+ | (if (.env | length) == 0 then del(.env) else . end)
135
+ else . end
136
+ | (if (.statusLine.command // "" | tostring | endswith("cc-statusline.sh"))
137
+ then del(.statusLine) else . end)
138
+ | (if (.apiKeyHelper // "" | tostring | endswith("weave-key.sh"))
139
+ then del(.apiKeyHelper) else . end)
140
+ ' "$settings_file")"
141
+ printf '%s\n' "$cleaned" >"$settings_file"
142
+ ok "Cleaned $settings_file"
143
+ else
144
+ info "No settings file at $settings_file (already uninstalled?)"
145
+ fi
146
+
147
+ if [ -n "$local_settings_file" ] && [ -f "$local_settings_file" ]; then
148
+ cleaned="$(jq '
149
+ if .env then
150
+ .env |= (del(.ANTHROPIC_AUTH_TOKEN, .ANTHROPIC_CUSTOM_HEADERS))
151
+ | (if (.env | length) == 0 then del(.env) else . end)
152
+ else . end
153
+ | del(.apiKeyHelper)
154
+ ' "$local_settings_file")"
155
+ printf '%s\n' "$cleaned" >"$local_settings_file"
156
+ ok "Cleaned $local_settings_file"
157
+ fi
158
+
159
+ for f in "$statusline_file"; do
160
+ if [ -f "$f" ]; then
161
+ rm -f "$f"
162
+ ok "Removed $f"
163
+ fi
164
+ done
165
+
166
+ if [ -n "$install_dir" ]; then
167
+ ok "Weave Router uninstalled from $install_dir."
168
+ else
169
+ ok "Weave Router uninstalled (scope=$scope)."
170
+ fi