@workweave/router 0.2.10 → 0.2.11

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 CHANGED
@@ -54,6 +54,19 @@ slash commands. The shell equivalent is `npx @workweave/router disable-routing`.
54
54
  Cursor has no config file we own — toggle its base URL override in **Settings →
55
55
  Models** instead.
56
56
 
57
+ Pick which models the router is allowed to route to:
58
+
59
+ ```bash
60
+ npx @workweave/router models --claude # list every model, with its on/off state
61
+ npx @workweave/router models disable gpt-5.6 --claude # take one out of rotation
62
+ npx @workweave/router models enable gpt-5.6 --claude # put it back
63
+ ```
64
+
65
+ Inside Claude Code that's `/router-models` (alias `/models`). Editing needs a
66
+ router that serves the model-selection API; against the Weave-hosted router the
67
+ list still prints and points you at the dashboard, where model selection is an
68
+ organization-wide setting.
69
+
57
70
  Uninstall:
58
71
 
59
72
  ```bash
@@ -82,9 +95,8 @@ Four install targets:
82
95
  - **Codex** (`--codex`) — patches `~/.codex/config.toml` (or
83
96
  `<repo>/.codex/config.toml`) with a managed `[model_providers.weave]`
84
97
  block plus `model_provider = "weave"`. The provider preserves the existing
85
- ChatGPT OAuth login. The public hosted endpoint sends
86
- `X-Weave-Router-Strategy: hmm`; `--local` and custom self-hosted URLs keep
87
- their router's configured default because its HMM sidecar is optional. HMM or forced
98
+ ChatGPT OAuth login. No install pins `X-Weave-Router-Strategy`; every
99
+ endpoint keeps its router's configured default. HMM or forced
88
100
  `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` turns use that plan;
89
101
  every other selected model uses its WorkWeave deployment or BYOK credential.
90
102
  The block lives between begin/end markers
package/cc-statusline.sh CHANGED
@@ -159,6 +159,23 @@ weave_command_tracked_by_git() {
159
159
  git -C "$(dirname "$1")" ls-files --error-unmatch -- "$1" >/dev/null 2>&1
160
160
  }
161
161
 
162
+ # weave_installed_command_names lists the wrappers this install may refresh.
163
+ # The statusline ships standalone (no registry.sh beside it), so the installed
164
+ # set — itself written from the registry — is the only source of truth here.
165
+ #
166
+ # Files written before ownership markers existed carry none, so matching on the
167
+ # marker alone would freeze every pre-marker install out of refreshes forever.
168
+ # List every wrapper instead and let the baseline comparison below decide: a
169
+ # file is replaced only when its bytes still match the last canonical copy, so
170
+ # a user-authored command is never touched whether or not it carries a marker.
171
+ weave_installed_command_names() {
172
+ local dir="$1" file
173
+ for file in "$dir"/*.md; do
174
+ [ -f "$file" ] || continue
175
+ printf '%s\n' "$(basename "$file" .md)"
176
+ done
177
+ }
178
+
162
179
  # weave_render_command prints $1 with the installer's {{SCOPE}} placeholder
163
180
  # replaced by $2, matching how install_slash_commands writes the same file.
164
181
  # Trailing newlines are stripped on both sides of every comparison below.
@@ -228,8 +245,7 @@ weave_sync_commands() {
228
245
  # Detach stdin (CC pipes JSON to us) so curl can't consume it, and silence
229
246
  # everything so no output leaks into the statusline.
230
247
  exec </dev/null
231
- for name in force-model unforce-model router-feedback fm ufm rf \
232
- router-off router-on router-status router-session; do
248
+ while IFS= read -r name; do
233
249
  installed="$cmd_dir/$name.md"
234
250
  # Only ever refresh a wrapper that is already installed: a missing one
235
251
  # was uninstalled or deliberately deleted, and resurrecting it would be
@@ -258,10 +274,10 @@ weave_sync_commands() {
258
274
  if [ -f "$prev" ]; then
259
275
  new_body="$(weave_render_command "$raw" "$scope_args")"
260
276
  prev_body="$(weave_render_command "$prev" "$scope_args")"
261
- installed_body="$(cat "$installed" 2>/dev/null)" || installed_body=""
277
+ installed_body="$(cat "$installed" 2>/dev/null | sed '/^<!-- weave-router managed command: .* -->$/d')" || installed_body=""
262
278
  if [ "$prev_body" = "$installed_body" ] && [ "$new_body" != "$installed_body" ]; then
263
279
  tmp="$installed.tmp.$$"
264
- if printf '%s\n' "$new_body" >"$tmp" 2>/dev/null; then
280
+ if printf '%s\n<!-- weave-router managed command: %s -->' "$new_body" "$name" >"$tmp" 2>/dev/null; then
265
281
  mv "$tmp" "$installed" 2>/dev/null || rm -f "$tmp"
266
282
  else
267
283
  rm -f "$tmp"
@@ -269,13 +285,152 @@ weave_sync_commands() {
269
285
  fi
270
286
  fi
271
287
  mv "$raw" "$prev" 2>/dev/null || rm -f "$raw"
272
- done
288
+ done <<EOF
289
+ $(weave_installed_command_names "$cmd_dir")
290
+ EOF
273
291
  ) >/dev/null 2>&1 &
274
292
  disown 2>/dev/null || true
275
293
  return 0
276
294
  }
277
295
  weave_sync_commands 2>/dev/null || true
278
296
 
297
+ # ---------- org "hide terminal surfaces" gate ----------
298
+ #
299
+ # When the org has hidden the router's terminal surfaces, the statusline
300
+ # renders nothing: this prints no output and exits 0, leaving the slot blank.
301
+ # The setting comes from GET /v1/display-settings with the install's own
302
+ # router key, but the foreground path NEVER touches the network: it decides
303
+ # solely from a per-install cache file (TTL WEAVE_DISPLAY_SETTINGS_TTL_SECONDS,
304
+ # default 1h), and a missing or stale cache fails open — the statusline
305
+ # renders normally. When the cache is missing or stale a detached background
306
+ # refresh re-fetches the setting and rewrites the cache atomically, so the
307
+ # next turn picks up the fresh value; a refresh failure simply leaves the
308
+ # cache stale, which keeps failing open rather than pinning the gate closed.
309
+ #
310
+ # Claude Code runs the statusline every turn, so several invocations can see a
311
+ # stale cache at once. Letting each fetch independently is not safe: the
312
+ # responses can land out of order, and the loser's mv would replace a newer
313
+ # setting with an older one AND stamp it fresh, pinning the gate on a stale
314
+ # value for a full TTL. Refreshes are therefore serialized per cache key on a
315
+ # mkdir mutex (atomic everywhere; flock is absent on macOS), held across both
316
+ # the fetch and the write. A refresh that finds the mutex held exits rather
317
+ # than queueing — the in-flight one is at least as fresh as anything it would
318
+ # fetch, so waiting only to overwrite it is the bug. The one path that is not
319
+ # strictly exclusive is reclaiming a lock whose holder died; see below.
320
+ weave_hidden_gate() {
321
+ command -v curl >/dev/null 2>&1 || return 1
322
+ command -v jq >/dev/null 2>&1 || return 1
323
+
324
+ local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/weave-router"
325
+ mkdir -p "$cache_dir" 2>/dev/null || return 1
326
+ local self="${BASH_SOURCE[0]:-$0}"
327
+ local script_slug
328
+ script_slug="$(printf '%s' "$self" | tr -c 'A-Za-z0-9._-' '_')"
329
+ local cache="$cache_dir/display-settings${script_slug}"
330
+ local ttl="${WEAVE_DISPLAY_SETTINGS_TTL_SECONDS:-3600}"
331
+
332
+ local now mtime fresh="false"
333
+ now="$(date +%s 2>/dev/null)" || now=0
334
+ if [ -f "$cache" ]; then
335
+ mtime="$(stat -c %Y "$cache" 2>/dev/null || stat -f %m "$cache" 2>/dev/null)" || mtime=0
336
+ if [ -n "${mtime:-}" ] && [ "$mtime" -gt 0 ] && [ $(( now - mtime )) -lt "$ttl" ]; then
337
+ fresh="true"
338
+ fi
339
+ fi
340
+
341
+ # Foreground decision: only a fresh cache hides the surfaces. Anything else
342
+ # (no cache, stale cache, unreadable cache) renders normally so a slow or
343
+ # unreachable router can never stall a turn or wedge the statusline blank.
344
+ if [ "$fresh" = "true" ]; then
345
+ [ "$(cat "$cache" 2>/dev/null)" = "1" ]
346
+ return
347
+ fi
348
+
349
+ # Background refresh for the next invocation. Resolve the router base URL
350
+ # and key inside the subshell from the Claude Code settings the installer
351
+ # wrote: project/--dir installs put both under <base>/.claude alongside
352
+ # this script (key in settings.local.json), while a user-scope install
353
+ # lives under ~/.weave and reads ~/.claude. Resolve relative to the
354
+ # script's own location, falling back to user scope, so a project install
355
+ # never reads (or leaks) the user-scope key. ANTHROPIC_BASE_URL and
356
+ # WEAVE_ROUTER_BASE_URL may also be set in the environment. A file:// base
357
+ # URL is the offline/test seam: curl reads it as the response body
358
+ # directly, so the endpoint path is meaningless for it; real router URLs
359
+ # (https) get /v1/display-settings appended.
360
+ (
361
+ exec </dev/null
362
+
363
+ # Take the per-cache-key mutex, or bail. mkdir is the portable atomic
364
+ # test-and-set. A crashed holder would otherwise block refreshes forever,
365
+ # so a lock older than the fetch timeout is treated as abandoned and
366
+ # reclaimed. Releasing from a trap covers every exit path below.
367
+ lock="$cache.lock"
368
+ if ! mkdir "$lock" 2>/dev/null; then
369
+ lock_mtime="$(stat -c %Y "$lock" 2>/dev/null || stat -f %m "$lock" 2>/dev/null)" || lock_mtime=0
370
+ lock_now="$(date +%s 2>/dev/null)" || lock_now=0
371
+ if [ "${lock_mtime:-0}" -le 0 ] || [ $(( lock_now - lock_mtime )) -le 30 ]; then
372
+ exit 0
373
+ fi
374
+ # Reclaiming an abandoned lock must not be delete-then-recreate. With
375
+ # `rm -rf` + `mkdir`, refreshers that all see the same stale lock each
376
+ # delete the next one's freshly created directory, so many end up holding
377
+ # it at once (measured at 50 claimants: 4-11 concurrent holders) and their
378
+ # writes can land out of order. Renaming is atomic, so only one racer can
379
+ # move a given directory aside and the losers exit instead of clobbering
380
+ # the winner (same measurement: 1-2). It is not a perfect mutex — a
381
+ # straggler can still reclaim the new lock a winner just created, since
382
+ # nothing distinguishes it from the stale one — but real refreshes arrive
383
+ # one per turn rather than 50 at once, and the staleness threshold below
384
+ # is what bounds the rest.
385
+ dead="$lock.dead.$$"
386
+ mv "$lock" "$dead" 2>/dev/null || exit 0
387
+ rm -rf "$dead" 2>/dev/null
388
+ mkdir "$lock" 2>/dev/null || exit 0
389
+ fi
390
+ trap 'rmdir "$lock" 2>/dev/null' EXIT
391
+
392
+ self_dir="$(cd "$(dirname "$self")" 2>/dev/null && pwd)"
393
+ settings_base="$HOME"
394
+ case "$self_dir" in
395
+ */.claude) settings_base="${self_dir%/.claude}" ;;
396
+ esac
397
+ settings="$settings_base/.claude/settings.json"
398
+ local_settings="$settings_base/.claude/settings.local.json"
399
+ base_url="${WEAVE_ROUTER_BASE_URL:-${ANTHROPIC_BASE_URL:-}}"
400
+ key="${WEAVE_ROUTER_KEY:-}"
401
+ if [ -z "$key" ] && [ -f "$settings" ]; then
402
+ key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$settings" 2>/dev/null | head -n1)"
403
+ fi
404
+ if [ -z "$key" ] && [ -f "$local_settings" ]; then
405
+ key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$local_settings" 2>/dev/null | head -n1)"
406
+ fi
407
+ if [ -z "$base_url" ] && [ -f "$settings" ]; then
408
+ base_url="$(jq -r '.env.ANTHROPIC_BASE_URL // empty' "$settings" 2>/dev/null)"
409
+ fi
410
+ [ -n "$base_url" ] && [ -n "$key" ] || exit 0
411
+ url="${base_url%/}"
412
+ case "$url" in
413
+ file://*) ;;
414
+ *) url="$url/v1/display-settings" ;;
415
+ esac
416
+ body="$(curl -fsS --max-time 5 -H "X-Weave-Router-Key: $key" "$url" 2>/dev/null)" || exit 0
417
+ hidden="$(printf '%s' "$body" | jq -r '.hide_terminal_surfaces // false' 2>/dev/null)"
418
+ tmp="$cache.tmp.$$"
419
+ if [ "$hidden" = "true" ]; then
420
+ printf '1' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
421
+ else
422
+ printf '0' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
423
+ fi
424
+ rm -f "$tmp" 2>/dev/null
425
+ ) >/dev/null 2>&1 &
426
+ disown 2>/dev/null || true
427
+ return 1
428
+ }
429
+
430
+ if weave_hidden_gate </dev/null; then
431
+ exit 0
432
+ fi
433
+
279
434
  input="$(cat)"
280
435
  transcript_path="$(printf '%s' "$input" | jq -r '.transcript_path // empty')"
281
436
  # Prefer model.id over display_name: pricing keys + the routed model id in
@@ -316,6 +471,7 @@ prices='{
316
471
  "claude-sonnet-5": 0.003,
317
472
  "deepseek/deepseek-v4-flash": 0.0001134,
318
473
  "deepseek/deepseek-v4-pro": 0.00174,
474
+ "deepseek/deepseek-v4-pro-0813": 0.00174,
319
475
  "gemini-2.0-flash": 0.0001,
320
476
  "gemini-2.0-flash-lite": 0.000075,
321
477
  "gemini-2.5-flash": 0.0003,
@@ -328,6 +484,7 @@ prices='{
328
484
  "gemini-3.5-flash": 0.0015,
329
485
  "gemini-3.5-flash-lite": 0.0003,
330
486
  "gemini-3.6-flash": 0.0015,
487
+ "gemini-3.7-flash": 0.0015,
331
488
  "google/gemma-4-26b-a4b-it": 0.00015,
332
489
  "gpt-4.1": 0.002,
333
490
  "gpt-4.1-mini": 0.0004,
@@ -347,7 +504,9 @@ prices='{
347
504
  "gpt-5.5-nano": 0.00015,
348
505
  "gpt-5.5-pro": 0.03,
349
506
  "gpt-5.6-luna": 0.001,
507
+ "gpt-5.6-luna-pro": 0.001,
350
508
  "gpt-5.6-sol": 0.005,
509
+ "gpt-5.6-sol-pro": 0.005,
351
510
  "gpt-5.6-terra": 0.0025,
352
511
  "grok-4.5": 0.002,
353
512
  "grok-4.6": 0.002,
@@ -370,7 +529,8 @@ prices='{
370
529
  "xiaomi/mimo-v2.5-pro": 0.001,
371
530
  "z-ai/glm-5": 0.001,
372
531
  "z-ai/glm-5.1": 0.0014,
373
- "z-ai/glm-5.2": 0.0014
532
+ "z-ai/glm-5.2": 0.0014,
533
+ "z-ai/glm-5.3-flash": 0.00015
374
534
  },
375
535
  "output": {
376
536
  "claude-fable-5": 0.05,
@@ -387,6 +547,7 @@ prices='{
387
547
  "claude-sonnet-5": 0.015,
388
548
  "deepseek/deepseek-v4-flash": 0.0002791,
389
549
  "deepseek/deepseek-v4-pro": 0.00348,
550
+ "deepseek/deepseek-v4-pro-0813": 0.00348,
390
551
  "gemini-2.0-flash": 0.0004,
391
552
  "gemini-2.0-flash-lite": 0.0003,
392
553
  "gemini-2.5-flash": 0.0012,
@@ -399,6 +560,7 @@ prices='{
399
560
  "gemini-3.5-flash": 0.009,
400
561
  "gemini-3.5-flash-lite": 0.0025,
401
562
  "gemini-3.6-flash": 0.0075,
563
+ "gemini-3.7-flash": 0.0075,
402
564
  "google/gemma-4-26b-a4b-it": 0.0006,
403
565
  "gpt-4.1": 0.008,
404
566
  "gpt-4.1-mini": 0.0016,
@@ -418,7 +580,9 @@ prices='{
418
580
  "gpt-5.5-nano": 0.0006,
419
581
  "gpt-5.5-pro": 0.18,
420
582
  "gpt-5.6-luna": 0.006,
583
+ "gpt-5.6-luna-pro": 0.006,
421
584
  "gpt-5.6-sol": 0.03,
585
+ "gpt-5.6-sol-pro": 0.03,
422
586
  "gpt-5.6-terra": 0.015,
423
587
  "grok-4.5": 0.006,
424
588
  "grok-4.6": 0.006,
@@ -441,7 +605,8 @@ prices='{
441
605
  "xiaomi/mimo-v2.5-pro": 0.003,
442
606
  "z-ai/glm-5": 0.0032,
443
607
  "z-ai/glm-5.1": 0.0044,
444
- "z-ai/glm-5.2": 0.0044
608
+ "z-ai/glm-5.2": 0.0044,
609
+ "z-ai/glm-5.3-flash": 0.0005
445
610
  }
446
611
  }'
447
612
  # END_GENERATED_PRICES
@@ -496,6 +661,12 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
496
661
  # * unrecognized model → "… isn't a recognized model · keeping
497
662
  # automatic routing" — a NO-OP: the prior
498
663
  # pin, if any, is left untouched
664
+ # * listing (historical) → "… pick a model by id …" / "no models are
665
+ # available to pin …" — also NO-OPs. The
666
+ # router no longer emits these, but a
667
+ # transcript written while it did still
668
+ # carries them, and they must not read as
669
+ # a clear or a live pin loses its [forced]
499
670
  # These persist on disk (the ingress stripper only scrubs them from upstream
500
671
  # requests). Classify each weave-router turn newest-first, skip the no-op
501
672
  # "rejected" acks, and let the latest real state change decide: an "applied"
@@ -509,6 +680,7 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
509
680
  | ([.message.content[]? | select(.type? == "text") | .text] | join(" ") | gsub("[\n\r]"; " ")) as $t
510
681
  | if ($t | test("force-model applied:")) then "APPLIED " + ($t | capture("force-model applied: (?<m>[^ ]+)").m)
511
682
  elif ($t | test("isn.t a recognized model")) then "REJECTED"
683
+ elif ($t | test("pick a model by id|no models are available to pin")) then "REJECTED"
512
684
  else "CLEARED" end' 2>/dev/null \
513
685
  | grep -m1 -v '^REJECTED$' || true)"
514
686
  if [[ "$force_state" == APPLIED\ * ]]; then
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: force-model
3
+ description: "Pin this Codex session to a specific model through the Weave Router."
4
+ ---
5
+
6
+ <!-- weave-router managed force-model skill -->
7
+
8
+ When the user invokes `$force-model <model-id>` (or asks to use the `$fm` alias), send a normal user message whose first character is one literal space, followed by `/force-model ` and the requested model id. Do not use a Codex slash command and do not omit the leading space. For example, send exactly:
9
+
10
+ ```text
11
+ /force-model gpt-5.6-terra
12
+ ```
13
+
14
+ Preserve any model id exactly as provided. Report the router's response after it returns.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: router-feedback
3
+ description: "Submit feedback about a Weave Router decision or model performance."
4
+ ---
5
+
6
+ <!-- weave-router managed router-feedback skill -->
7
+
8
+ When the user invokes `$router-feedback <feedback>` (or asks to use the `$rf` alias), send a normal user message whose first character is one literal space, followed by `/router-feedback ` and the feedback text. Do not use a Codex slash command and do not omit the leading space. Preserve the feedback text exactly. For example:
9
+
10
+ ```text
11
+ /router-feedback the selected model struggled with this task
12
+ ```
13
+
14
+ Report the router's response after it returns.
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: unforce-model
3
+ description: "Clear the active Weave Router model pin for this Codex session."
4
+ ---
5
+
6
+ <!-- weave-router managed unforce-model skill -->
7
+
8
+ When the user invokes `$unforce-model` (or asks to use the `$ufm` alias), send a normal user message whose first character is one literal space, followed by `/unforce-model`. Do not use a Codex slash command and do not omit the leading space. Send exactly:
9
+
10
+ ```text
11
+ /unforce-model
12
+ ```
13
+
14
+ Report the router's response after it returns.
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: Alias for /router-models — list the models the Weave Router may route to, and turn them on or off.
3
+ argument-hint: [model or provider to enable/disable]
4
+ allowed-tools: Bash(npx:*)
5
+ ---
6
+
7
+ Show me which models this installation lets the Weave Router pick from, and
8
+ change that selection when I ask. This is the same list — and the same stored
9
+ setting — as the checkboxes on the router dashboard's settings page.
10
+
11
+ Start by running:
12
+
13
+ `npx @workweave/router models --claude{{SCOPE}}`
14
+
15
+ That prints every deployed model grouped by provider, with `[x]` for models the
16
+ router may pick and `[ ]` for models it may not. Present it back to me as a
17
+ compact checklist in that same `[x]` / `[ ]` form, keeping the provider
18
+ grouping and the exact model ids — I select models by id.
19
+
20
+ Then:
21
+
22
+ - If I named models or providers in `$ARGUMENTS`, work out whether I want them
23
+ on or off from how I phrased it, and apply it with
24
+ `npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
25
+ `npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
26
+ `providers` before `enable`/`disable` to switch a whole provider). Several
27
+ ids can go in one call. Then re-run the list and show me the result.
28
+ - If I named nothing, stop after the list and ask which ones I want to change.
29
+ Don't change anything I didn't ask for.
30
+
31
+ Other things I might ask for:
32
+
33
+ - Rank models by preference:
34
+ `npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
35
+ matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
36
+ drop the ranking.
37
+ - Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
38
+
39
+ If the command reports that this router doesn't expose model selection, that's
40
+ a Weave-hosted router: model selection belongs to the whole organization there,
41
+ so tell me to change it at https://router.workweave.ai/dashboard/settings —
42
+ don't try to work around it. Its listing carries no on/off state, so present it
43
+ as a plain list; don't infer which models are enabled.
44
+
45
+ Disabling a model takes effect on the router's next routing decision; no
46
+ restart is needed.
@@ -0,0 +1,46 @@
1
+ ---
2
+ description: List the models the Weave Router may route to, and turn them on or off.
3
+ argument-hint: [model or provider to enable/disable]
4
+ allowed-tools: Bash(npx:*)
5
+ ---
6
+
7
+ Show me which models this installation lets the Weave Router pick from, and
8
+ change that selection when I ask. This is the same list — and the same stored
9
+ setting — as the checkboxes on the router dashboard's settings page.
10
+
11
+ Start by running:
12
+
13
+ `npx @workweave/router models --claude{{SCOPE}}`
14
+
15
+ That prints every deployed model grouped by provider, with `[x]` for models the
16
+ router may pick and `[ ]` for models it may not. Present it back to me as a
17
+ compact checklist in that same `[x]` / `[ ]` form, keeping the provider
18
+ grouping and the exact model ids — I select models by id.
19
+
20
+ Then:
21
+
22
+ - If I named models or providers in `$ARGUMENTS`, work out whether I want them
23
+ on or off from how I phrased it, and apply it with
24
+ `npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
25
+ `npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
26
+ `providers` before `enable`/`disable` to switch a whole provider). Several
27
+ ids can go in one call. Then re-run the list and show me the result.
28
+ - If I named nothing, stop after the list and ask which ones I want to change.
29
+ Don't change anything I didn't ask for.
30
+
31
+ Other things I might ask for:
32
+
33
+ - Rank models by preference:
34
+ `npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
35
+ matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
36
+ drop the ranking.
37
+ - Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
38
+
39
+ If the command reports that this router doesn't expose model selection, that's
40
+ a Weave-hosted router: model selection belongs to the whole organization there,
41
+ so tell me to change it at https://router.workweave.ai/dashboard/settings —
42
+ don't try to work around it. Its listing carries no on/off state, so present it
43
+ as a plain list; don't infer which models are enabled.
44
+
45
+ Disabling a model takes effect on the router's next routing decision; no
46
+ restart is needed.
package/directives.tsv ADDED
@@ -0,0 +1,12 @@
1
+ # Weave Router directive registry
2
+ # canonical|aliases|capability|claude|codex|opencode|pi|cursor|adapter
3
+ # aliases are comma-separated; client columns are yes/no. adapter is the native asset kind.
4
+ force-model|fm|prompt|yes|yes|yes|yes|manual|command,skill
5
+ unforce-model|ufm|prompt|yes|yes|yes|yes|manual|command,skill
6
+ router-feedback|rf|prompt|yes|yes|yes|no|manual|command,skill
7
+ router-off||local-toggle|yes|no|no|no|manual|command
8
+ router-on||local-toggle|yes|no|no|no|manual|command
9
+ router-status||local-toggle|yes|no|no|no|manual|command
10
+ router-session||prompt|yes|no|no|no|manual|command
11
+ router-models|models|local-toggle|yes|no|no|no|manual|command
12
+ disable-routing||local-toggle|no|yes|no|no|manual|skill