@workweave/router 0.1.7 → 0.2.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/install.sh +342 -19
- package/package.json +25 -3
- package/pi-router/README.md +70 -0
- package/pi-router/package.json +13 -0
- package/pi-router/src/compaction.ts +26 -0
- package/pi-router/src/config.ts +261 -0
- package/pi-router/src/dispatch.ts +344 -0
- package/pi-router/src/index.ts +49 -0
- package/pi-router/src/metadata.ts +32 -0
- package/pi-router/src/provider.ts +51 -0
- package/pi-router/src/routed-model.ts +31 -0
- package/pi-router/src/safety.ts +66 -0
- package/uninstall.sh +114 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @workweave/router — route the pi coding agent through the WorkWeave Router.
|
|
3
|
+
*
|
|
4
|
+
* Wiring (all on the existing router surface — no router source change beyond
|
|
5
|
+
* the installer):
|
|
6
|
+
* - provider: register `weave` with per-process knob headers (quality on
|
|
7
|
+
* the main loop, speed/cheap in subagents).
|
|
8
|
+
* - metadata: stamp body.metadata.user_id for sticky sessions + subagent
|
|
9
|
+
* detection.
|
|
10
|
+
* - routed-model: show which model the router actually picked.
|
|
11
|
+
* - safety: block catastrophic bash (unless WEAVE_NO_SAFETY=1).
|
|
12
|
+
* - compaction: experimental cheap path (only when WEAVE_CHEAP_COMPACTION=1).
|
|
13
|
+
* - dispatch: parallel, context-isolated subagents — top-level process
|
|
14
|
+
* only (no grandchildren).
|
|
15
|
+
*
|
|
16
|
+
* The same module loads in dispatched children via `-e <self>`; WEAVE_PI_SUBAGENT
|
|
17
|
+
* flips the provider knobs and suppresses the dispatch tool so fan-out doesn't recurse.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
22
|
+
import { isSubagent } from "./config.js";
|
|
23
|
+
import { registerCheapCompaction } from "./compaction.js";
|
|
24
|
+
import { registerDispatch } from "./dispatch.js";
|
|
25
|
+
import { registerMetadata } from "./metadata.js";
|
|
26
|
+
import { registerRoutedModel } from "./routed-model.js";
|
|
27
|
+
import { registerSafety } from "./safety.js";
|
|
28
|
+
import { registerWeave } from "./provider.js";
|
|
29
|
+
|
|
30
|
+
const SELF_PATH = fileURLToPath(import.meta.url);
|
|
31
|
+
|
|
32
|
+
export default function (pi: ExtensionAPI): void {
|
|
33
|
+
// Register at load so the provider is available for `--list-models` and
|
|
34
|
+
// print mode (dispatched children), and again on session_start so the right
|
|
35
|
+
// knob headers survive `/reload` and new/resumed sessions.
|
|
36
|
+
registerWeave(pi);
|
|
37
|
+
pi.on("session_start", () => registerWeave(pi));
|
|
38
|
+
|
|
39
|
+
registerMetadata(pi);
|
|
40
|
+
registerRoutedModel(pi);
|
|
41
|
+
|
|
42
|
+
if (process.env.WEAVE_NO_SAFETY !== "1") registerSafety(pi);
|
|
43
|
+
if (process.env.WEAVE_CHEAP_COMPACTION === "1") registerCheapCompaction(pi);
|
|
44
|
+
|
|
45
|
+
// Only the top-level process fans out. Children (WEAVE_PI_SUBAGENT=1) load
|
|
46
|
+
// this same extension but get no dispatch tool, so subagents can't spawn
|
|
47
|
+
// grandchildren.
|
|
48
|
+
if (!isSubagent()) registerDispatch(pi, SELF_PATH);
|
|
49
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injects `metadata.user_id` into the request body so the router can:
|
|
3
|
+
* - keep the main loop on a sticky session pin ("pi:<sessionId>"), and
|
|
4
|
+
* - detect subagents ("subagent:<uuid>") for an independent pin + server-side
|
|
5
|
+
* SubAgentDispatch handling.
|
|
6
|
+
*
|
|
7
|
+
* This is the one body-level signal we control (the session pin key derives
|
|
8
|
+
* from metadata.user_id when present; subagent detection on the Anthropic
|
|
9
|
+
* ingress path keys off a "subagent:" prefix). Headers can't carry it because
|
|
10
|
+
* before_provider_request can't set headers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import { isSubagent } from "./config.js";
|
|
16
|
+
|
|
17
|
+
// One id per child process so all of a subagent's requests share a single pin.
|
|
18
|
+
// The parent passes WEAVE_PI_SUBAGENT_ID; a standalone child falls back to a uuid.
|
|
19
|
+
const SUBAGENT_USER_ID = `subagent:${process.env.WEAVE_PI_SUBAGENT_ID?.trim() || randomUUID()}`;
|
|
20
|
+
|
|
21
|
+
export function registerMetadata(pi: ExtensionAPI): void {
|
|
22
|
+
pi.on("before_provider_request", (event, ctx: ExtensionContext) => {
|
|
23
|
+
const body = event.payload as { metadata?: { user_id?: string } } | undefined;
|
|
24
|
+
if (!body || typeof body !== "object") return undefined;
|
|
25
|
+
|
|
26
|
+
const userId = isSubagent() ? SUBAGENT_USER_ID : `pi:${ctx.sessionManager.getSessionId()}`;
|
|
27
|
+
if (body.metadata?.user_id === userId) return undefined;
|
|
28
|
+
|
|
29
|
+
body.metadata = { ...(body.metadata ?? {}), user_id: userId };
|
|
30
|
+
return body;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers the `weave` provider with the per-process header policy.
|
|
3
|
+
*
|
|
4
|
+
* Re-registered on each `session_start` (and once at load) so the right knob
|
|
5
|
+
* headers are always live and the provider survives `/reload`. We register a
|
|
6
|
+
* new provider named "weave" rather than overriding the built-in "anthropic"
|
|
7
|
+
* provider — overriding "anthropic" would hijack the Claude OAuth token.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import {
|
|
12
|
+
getRole,
|
|
13
|
+
getRouterBaseUrl,
|
|
14
|
+
isSubagent,
|
|
15
|
+
PROVIDER_NAME,
|
|
16
|
+
providerHeaders,
|
|
17
|
+
resolveRouterKey,
|
|
18
|
+
WEAVE_MODELS,
|
|
19
|
+
} from "./config.js";
|
|
20
|
+
|
|
21
|
+
export function registerWeave(pi: ExtensionAPI): void {
|
|
22
|
+
const key = resolveRouterKey();
|
|
23
|
+
const role = getRole();
|
|
24
|
+
|
|
25
|
+
if (!key) {
|
|
26
|
+
// The main loop can still run off the installer-written models.json
|
|
27
|
+
// provider. A subagent MUST apply the speed/cheap knobs, so a missing
|
|
28
|
+
// key there is fatal rather than silently routing on quality knobs.
|
|
29
|
+
if (isSubagent()) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Weave: no router key found (set WEAVE_ROUTER_KEY or write ~/.pi/agent/.weave_router_key).",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pi.registerProvider(PROVIDER_NAME, {
|
|
38
|
+
name: "Weave Router",
|
|
39
|
+
// Root URL, no /v1: the anthropic-messages provider uses @anthropic-ai/sdk,
|
|
40
|
+
// which appends /v1/messages to baseUrl. A /v1 here yields /v1/v1/messages.
|
|
41
|
+
baseUrl: getRouterBaseUrl(),
|
|
42
|
+
// Planted to satisfy pi's "is auth configured" check. The router ignores
|
|
43
|
+
// it (auth runs off X-Weave-Router-Key); authHeader:false keeps
|
|
44
|
+
// Authorization free for BYOK.
|
|
45
|
+
apiKey: key,
|
|
46
|
+
api: "anthropic-messages",
|
|
47
|
+
authHeader: false,
|
|
48
|
+
headers: providerHeaders(role, key),
|
|
49
|
+
models: WEAVE_MODELS,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surfaces which model the router actually picked for each request.
|
|
3
|
+
*
|
|
4
|
+
* The router sets `x-router-model` on every response (streaming, non-streaming,
|
|
5
|
+
* and cache hits). In the interactive UI we show it in the status bar and
|
|
6
|
+
* notify on change. In a headless child (print/RPC — e.g. a dispatch subagent)
|
|
7
|
+
* there is no UI, so we print a marker to stderr that the parent dispatch tool
|
|
8
|
+
* parses to attribute each subagent's work to a model.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { ROUTED_MODEL_HEADER, ROUTED_MODEL_STDERR_PREFIX } from "./config.js";
|
|
13
|
+
|
|
14
|
+
const STATUS_KEY = "weave";
|
|
15
|
+
|
|
16
|
+
export function registerRoutedModel(pi: ExtensionAPI): void {
|
|
17
|
+
let last: string | undefined;
|
|
18
|
+
|
|
19
|
+
pi.on("after_provider_response", (event, ctx: ExtensionContext) => {
|
|
20
|
+
const model = event.headers?.[ROUTED_MODEL_HEADER];
|
|
21
|
+
if (!model || model === last) return;
|
|
22
|
+
last = model;
|
|
23
|
+
|
|
24
|
+
if (ctx.hasUI) {
|
|
25
|
+
ctx.ui.setStatus(STATUS_KEY, `routed: ${model}`);
|
|
26
|
+
ctx.ui.notify(`Weave routed to ${model}`, "info");
|
|
27
|
+
} else {
|
|
28
|
+
process.stderr.write(`${ROUTED_MODEL_STDERR_PREFIX} ${model}\n`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last-resort guard against a handful of catastrophic, effectively
|
|
3
|
+
* irreversible shell commands. pi already confirms normal tool calls; this
|
|
4
|
+
* matters most in non-interactive subagents where there is no human to
|
|
5
|
+
* confirm. It blocks ONLY the obviously-destructive forms below — it is a
|
|
6
|
+
* backstop, not a sandbox. Disable with WEAVE_NO_SAFETY=1.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { type ExtensionAPI, type ExtensionContext, isToolCallEventType, type ToolCallEvent } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
interface Rule {
|
|
12
|
+
test: (cmd: string) => boolean;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const hasRecursive = (c: string) => /(?:\s|^)-\w*r/i.test(c) || /--recursive\b/.test(c);
|
|
17
|
+
const hasForce = (c: string) => /(?:\s|^)-\w*f/i.test(c) || /--force\b/.test(c);
|
|
18
|
+
const targetsRoot = (c: string) =>
|
|
19
|
+
/--no-preserve-root\b/.test(c) || /(?:\s|^)(?:\/|~|\$HOME|\/\*)(?:\s|$)/.test(c);
|
|
20
|
+
|
|
21
|
+
const RULES: Rule[] = [
|
|
22
|
+
{
|
|
23
|
+
test: (c) => /\brm\b/.test(c) && hasRecursive(c) && hasForce(c) && targetsRoot(c),
|
|
24
|
+
reason: "recursive force-remove of a root/home path",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
test: (c) => /\bmkfs(\.\w+)?\b/.test(c),
|
|
28
|
+
reason: "filesystem format (mkfs)",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
test: (c) => /\bdd\b[^\n]*\bof=\/dev\//.test(c),
|
|
32
|
+
reason: "dd writing directly to a device",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
test: (c) => />\s*\/dev\/(?:sd|nvme|disk|hd|mapper)/.test(c),
|
|
36
|
+
reason: "redirect overwriting a raw block device",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
test: (c) => /:\s*\(\s*\)\s*\{[^}]*\|[^}]*&[^}]*\}\s*;\s*:/.test(c),
|
|
40
|
+
reason: "fork bomb",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
test: (c) =>
|
|
44
|
+
/\bgit\s+push\b/.test(c) &&
|
|
45
|
+
(/(?:--force(?!-with-lease)|\s-f\b)/.test(c) || /\+(?:main|master)\b/.test(c)) &&
|
|
46
|
+
/\b(?:main|master)\b/.test(c),
|
|
47
|
+
reason: "force-push to main/master",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function catastrophicReason(command: string): string | undefined {
|
|
52
|
+
const cmd = command.trim();
|
|
53
|
+
for (const rule of RULES) {
|
|
54
|
+
if (rule.test(cmd)) return rule.reason;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerSafety(pi: ExtensionAPI): void {
|
|
60
|
+
pi.on("tool_call", (event: ToolCallEvent, _ctx: ExtensionContext) => {
|
|
61
|
+
if (!isToolCallEventType("bash", event)) return undefined;
|
|
62
|
+
const reason = catastrophicReason(event.input.command ?? "");
|
|
63
|
+
if (!reason) return undefined;
|
|
64
|
+
return { block: true, reason: `Weave safety: blocked ${reason}. Set WEAVE_NO_SAFETY=1 to override.` };
|
|
65
|
+
});
|
|
66
|
+
}
|
package/uninstall.sh
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# Weave Router uninstaller for Claude Code, Codex, and
|
|
3
|
+
# Weave Router uninstaller for Claude Code, Codex, opencode, and pi.
|
|
4
4
|
#
|
|
5
5
|
# Default target is Claude Code: removes the env vars, statusLine, and local
|
|
6
6
|
# router auth that install.sh added; leaves the rest of settings.json
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
# config.toml — anything outside the markers is preserved. Pass --opencode
|
|
10
10
|
# to strip the `provider.weave` block (and the top-level `model` key when
|
|
11
11
|
# it points at the router) from opencode.json; other providers and user
|
|
12
|
-
# settings are preserved.
|
|
12
|
+
# settings are preserved. Pass --pi to strip the `weave` provider from
|
|
13
|
+
# pi's models.json, drop @workweave/router from settings.json, revert the
|
|
14
|
+
# weave defaults, and remove the router key file.
|
|
13
15
|
#
|
|
14
16
|
# Usage:
|
|
15
17
|
# npx @workweave/router --uninstall # Claude Code, user scope
|
|
16
18
|
# npx @workweave/router --uninstall --codex # Codex, user scope
|
|
17
19
|
# npx @workweave/router --uninstall --opencode # opencode, user scope
|
|
20
|
+
# npx @workweave/router --uninstall --pi # pi, user scope
|
|
18
21
|
# npx @workweave/router --uninstall --scope project # run inside the repo
|
|
19
22
|
# npx @workweave/router --uninstall --dir /tmp/test # --dir alone (user scope, .weave/)
|
|
20
23
|
# npx @workweave/router --uninstall --scope project --dir /tmp # --dir + project scope (.claude/)
|
|
@@ -59,6 +62,9 @@ while [ $# -gt 0 ]; do
|
|
|
59
62
|
--opencode)
|
|
60
63
|
target="opencode"; shift
|
|
61
64
|
;;
|
|
65
|
+
--pi)
|
|
66
|
+
target="pi"; shift
|
|
67
|
+
;;
|
|
62
68
|
--claude)
|
|
63
69
|
# No-op selector for symmetry with --codex / --opencode and install.sh's
|
|
64
70
|
# --claude. Lets `./install.sh --uninstall --claude` (which forwards
|
|
@@ -77,7 +83,7 @@ while [ $# -gt 0 ]; do
|
|
|
77
83
|
esac
|
|
78
84
|
done
|
|
79
85
|
|
|
80
|
-
if { [ "$target" = "claude" ] || [ "$target" = "opencode" ]; } && ! command -v jq >/dev/null 2>&1; then
|
|
86
|
+
if { [ "$target" = "claude" ] || [ "$target" = "opencode" ] || [ "$target" = "pi" ]; } && ! command -v jq >/dev/null 2>&1; then
|
|
81
87
|
err "jq is required for the $target uninstall path."
|
|
82
88
|
exit 1
|
|
83
89
|
fi
|
|
@@ -189,6 +195,111 @@ if [ "$target" = "opencode" ]; then
|
|
|
189
195
|
exit 0
|
|
190
196
|
fi
|
|
191
197
|
|
|
198
|
+
# ---------- pi uninstall path ----------
|
|
199
|
+
|
|
200
|
+
if [ "$target" = "pi" ]; then
|
|
201
|
+
# Resolve the pi agent dir based on scope/dir. Mirrors install.sh: user scope
|
|
202
|
+
# is pi's default ~/.pi/agent; project/--dir scope is a repo-local .pi.
|
|
203
|
+
if [ -n "$install_dir" ]; then
|
|
204
|
+
install_dir="$(cd "$install_dir" 2>/dev/null && pwd || echo "$install_dir")"
|
|
205
|
+
pi_dir="$install_dir/.pi"
|
|
206
|
+
refuse_if_symlink "$pi_dir"
|
|
207
|
+
elif [ "$scope" = "user" ]; then
|
|
208
|
+
pi_dir="$HOME/.pi/agent"
|
|
209
|
+
else
|
|
210
|
+
# Project scope: same prompt + git-root fallback as the opencode path.
|
|
211
|
+
project_dir=""
|
|
212
|
+
if [ "$scope_explicit" = "false" ] && [ -r /dev/tty ]; then
|
|
213
|
+
default_project_dir="$(pwd)"
|
|
214
|
+
printf "Project directory to uninstall from [default: %s]: " "$default_project_dir"
|
|
215
|
+
read -r project_dir_choice </dev/tty || project_dir_choice=""
|
|
216
|
+
project_dir="${project_dir_choice:-$default_project_dir}"
|
|
217
|
+
case "$project_dir" in
|
|
218
|
+
"~") project_dir="$HOME" ;;
|
|
219
|
+
"~/"*) project_dir="$HOME/${project_dir#~/}" ;;
|
|
220
|
+
esac
|
|
221
|
+
if [ ! -d "$project_dir" ]; then
|
|
222
|
+
err "directory does not exist: $project_dir"
|
|
223
|
+
exit 1
|
|
224
|
+
fi
|
|
225
|
+
project_dir="$(cd "$project_dir" && pwd)"
|
|
226
|
+
fi
|
|
227
|
+
if [ -n "${project_dir:-}" ]; then
|
|
228
|
+
pi_dir="$project_dir/.pi"
|
|
229
|
+
else
|
|
230
|
+
if ! git_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
|
|
231
|
+
err "--scope project must be run inside a git repo, or use --dir <path>."
|
|
232
|
+
exit 1
|
|
233
|
+
fi
|
|
234
|
+
pi_dir="$git_root/.pi"
|
|
235
|
+
fi
|
|
236
|
+
refuse_if_symlink "$pi_dir"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
pi_models_file="$pi_dir/models.json"
|
|
240
|
+
pi_settings_file="$pi_dir/settings.json"
|
|
241
|
+
pi_key_file="$pi_dir/.weave_router_key"
|
|
242
|
+
refuse_if_symlink "$pi_models_file"
|
|
243
|
+
refuse_if_symlink "$pi_settings_file"
|
|
244
|
+
refuse_if_symlink "$pi_key_file"
|
|
245
|
+
|
|
246
|
+
# models.json: drop provider.weave; remove the file if nothing else remains.
|
|
247
|
+
# Other providers/models the user added are preserved.
|
|
248
|
+
if [ -f "$pi_models_file" ]; then
|
|
249
|
+
cleaned="$(jq '
|
|
250
|
+
(if .providers.weave then del(.providers.weave) else . end)
|
|
251
|
+
| (if (.providers // {}) == {} then del(.providers) else . end)
|
|
252
|
+
' "$pi_models_file")"
|
|
253
|
+
printf '%s\n' "$cleaned" >"$pi_models_file"
|
|
254
|
+
if [ "$(jq -r 'keys | length' "$pi_models_file" 2>/dev/null || echo 0)" = "0" ]; then
|
|
255
|
+
rm -f "$pi_models_file"
|
|
256
|
+
ok "Removed empty $pi_models_file"
|
|
257
|
+
else
|
|
258
|
+
ok "Cleaned $pi_models_file"
|
|
259
|
+
fi
|
|
260
|
+
else
|
|
261
|
+
info "No pi models config at $pi_models_file (already uninstalled?)"
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
# settings.json: drop our package and revert defaults that still point at the
|
|
265
|
+
# router. Leaving defaultProvider="weave" after removing the provider would
|
|
266
|
+
# break pi startup, so reverting is the correct reverse of the install.
|
|
267
|
+
# defaultModel is reverted ONLY when defaultProvider was "weave" (the state
|
|
268
|
+
# install creates): install sets defaultModel only when it was empty, so a user
|
|
269
|
+
# who independently picked claude-sonnet-4-6 with their own provider keeps it.
|
|
270
|
+
if [ -f "$pi_settings_file" ]; then
|
|
271
|
+
cleaned="$(jq '
|
|
272
|
+
(if .packages then .packages -= ["npm:@workweave/router", "npm:@workweave/pi-router"] else . end)
|
|
273
|
+
| (if (.packages // []) == [] then del(.packages) else . end)
|
|
274
|
+
| (if .defaultProvider == "weave"
|
|
275
|
+
then del(.defaultProvider)
|
|
276
|
+
| (if .defaultModel == "claude-sonnet-4-6" then del(.defaultModel) else . end)
|
|
277
|
+
else . end)
|
|
278
|
+
' "$pi_settings_file")"
|
|
279
|
+
printf '%s\n' "$cleaned" >"$pi_settings_file"
|
|
280
|
+
if [ "$(jq -r 'keys | length' "$pi_settings_file" 2>/dev/null || echo 0)" = "0" ]; then
|
|
281
|
+
rm -f "$pi_settings_file"
|
|
282
|
+
ok "Removed empty $pi_settings_file"
|
|
283
|
+
else
|
|
284
|
+
ok "Cleaned $pi_settings_file"
|
|
285
|
+
fi
|
|
286
|
+
else
|
|
287
|
+
info "No pi settings at $pi_settings_file (already uninstalled?)"
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
if [ -f "$pi_key_file" ]; then
|
|
291
|
+
rm -f "$pi_key_file"
|
|
292
|
+
ok "Removed $pi_key_file"
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
if [ -n "$install_dir" ]; then
|
|
296
|
+
ok "Weave Router uninstalled from $install_dir (pi)."
|
|
297
|
+
else
|
|
298
|
+
ok "Weave Router uninstalled (pi, scope=$scope)."
|
|
299
|
+
fi
|
|
300
|
+
exit 0
|
|
301
|
+
fi
|
|
302
|
+
|
|
192
303
|
# ---------- codex uninstall path ----------
|
|
193
304
|
|
|
194
305
|
if [ "$target" = "codex" ]; then
|