@oneciel-ai/claude-any 0.1.106 → 0.1.107
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 +43 -5
- package/claude-any-tool-guard.py +61 -7
- package/claude_any.py +773 -189
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,6 +26,22 @@
|
|
|
26
26
|
|
|
27
27
|
## Today's Top 3 Benefits
|
|
28
28
|
|
|
29
|
+
### 2026-06-11
|
|
30
|
+
|
|
31
|
+
1. **OpenRouter provider** — [OpenRouter](https://openrouter.ai/) is a first-class
|
|
32
|
+
OpenAI-compatible provider with full feature parity (model discovery,
|
|
33
|
+
provider-options, status, help), defaulting to the free
|
|
34
|
+
`nvidia/nemotron-3-ultra-550b-a55b:free` model.
|
|
35
|
+
2. **Per-key cooldown for multi-key rotation** — when one key in a multi-key pool
|
|
36
|
+
hits a 429, Claude Any rests it until its rate limit resets (from
|
|
37
|
+
`X-RateLimit-Reset`/`Retry-After`) and keeps serving from the remaining keys,
|
|
38
|
+
then rejoins it automatically. Ideal for OpenRouter `:free` per-key limits.
|
|
39
|
+
3. **Faithful Anthropic native behavior** — routed Anthropic no longer overrides
|
|
40
|
+
Claude Code's native per-model output cap (e.g. Fable 5 keeps its 64k default
|
|
41
|
+
instead of a forced 4096), `/model` switching reaches the chosen model instead
|
|
42
|
+
of collapsing to the menu model, and the `?beta=true` flag is forwarded so
|
|
43
|
+
long-context (1M) requests are billed correctly.
|
|
44
|
+
|
|
29
45
|
### 2026-06-05
|
|
30
46
|
|
|
31
47
|
1. **Windows-aware install paths** — Claude Any now uses `%APPDATA%\claude-any`
|
|
@@ -105,7 +121,7 @@ Ollama Cloud (glm-5.1) streamed through the claude-any router with SSE word-boun
|
|
|
105
121
|
|
|
106
122
|
Claude Any is a provider selector and compatibility launcher for Claude Code.
|
|
107
123
|
It lets you choose Anthropic, Ollama, Ollama Cloud, DeepSeek.com, OpenCode Zen,
|
|
108
|
-
OpenCode Go, LM Studio,
|
|
124
|
+
OpenCode Go, OpenRouter, LM Studio,
|
|
109
125
|
vLLM, NVIDIA hosted models, or self-hosted NIM before Claude Code starts, then
|
|
110
126
|
passes normal Claude Code arguments through unchanged.
|
|
111
127
|
|
|
@@ -224,6 +240,19 @@ For OpenCode Go:
|
|
|
224
240
|
claude-any --ca-provider opencode-go --ca-base-url https://opencode.ai/zen/go --ca-model qwen3.6-plus --ca-api-key-env OPENCODE_GO_API_KEY --ca-provider-option endpoint:custom-model=chat --ca-no-launch
|
|
225
241
|
```
|
|
226
242
|
|
|
243
|
+
For [OpenRouter](https://openrouter.ai/) (OpenAI-compatible; access to hundreds of
|
|
244
|
+
models through one key). The default model is the free
|
|
245
|
+
`nvidia/nemotron-3-ultra-550b-a55b:free`; pick any slug from OpenRouter's catalog:
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
claude-any --ca-provider openrouter --ca-model nvidia/nemotron-3-ultra-550b-a55b:free --ca-api-key-env OPENROUTER_API_KEY --ca-no-launch
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
OpenRouter free (`:free`) models are rate-limited per key (e.g. 20 requests/min),
|
|
252
|
+
so this is a natural fit for multi-key round-robin (below): store several keys and
|
|
253
|
+
Claude Any spreads requests across them and rests any key that hits a 429 until
|
|
254
|
+
its rate limit resets.
|
|
255
|
+
|
|
227
256
|
`--ca-provider-option KEY=VALUE` applies a provider option to the current
|
|
228
257
|
provider; use `--ca-set-provider-option PROVIDER KEY=VALUE` when a script needs
|
|
229
258
|
to configure a provider other than the current one. OpenCode endpoint overrides
|
|
@@ -368,14 +397,23 @@ passthrough arguments are all configurable without opening the menu. API keys
|
|
|
368
397
|
can be passed directly with `--ca-api-key`, but `--ca-api-key-env` is safer for
|
|
369
398
|
scripts because the secret does not appear in shell history.
|
|
370
399
|
|
|
371
|
-
For high-token routed sessions such as ultracode,
|
|
372
|
-
|
|
400
|
+
For high-token routed sessions such as ultracode, or for rate-limited free models
|
|
401
|
+
like OpenRouter `:free`, store multiple keys for the same provider and Claude Any
|
|
402
|
+
will rotate them per upstream request:
|
|
373
403
|
|
|
374
404
|
```sh
|
|
375
|
-
export
|
|
376
|
-
claude-any --ca-provider
|
|
405
|
+
export OPENROUTER_API_KEYS="KEY1,KEY2,KEY3,KEY4"
|
|
406
|
+
claude-any --ca-provider openrouter --ca-api-keys-env OPENROUTER_API_KEYS --ca-no-launch
|
|
407
|
+
# or directly: claude-any set-api-keys openrouter KEY1,KEY2,KEY3,KEY4
|
|
377
408
|
```
|
|
378
409
|
|
|
410
|
+
Rotation is round-robin **with per-key cooldown**: if one key returns a 429, that
|
|
411
|
+
key rests until its rate limit resets — Claude Any reads the reset time from the
|
|
412
|
+
response (`X-RateLimit-Reset`, falling back to `Retry-After`), skips the resting
|
|
413
|
+
key, and keeps serving from the remaining keys; the key automatically rejoins the
|
|
414
|
+
rotation once its cooldown expires. A key that exhausts a daily quota rests until
|
|
415
|
+
the quota refreshes rather than retrying every hour.
|
|
416
|
+
|
|
379
417
|
This distributes provider quota/rate usage across keys; it does not reduce the
|
|
380
418
|
task's actual token consumption. The menu also accepts comma/newline-separated
|
|
381
419
|
keys in the API-key input paths and shows a masked primary key plus a short
|
package/claude-any-tool-guard.py
CHANGED
|
@@ -524,7 +524,9 @@ def handle_stop(event: dict[str, Any]) -> int:
|
|
|
524
524
|
return 0
|
|
525
525
|
session_id = str(event.get("session_id") or "")
|
|
526
526
|
transcript_path = str(event.get("transcript_path") or "")
|
|
527
|
-
|
|
527
|
+
# Also run the plan-idle block for bypass-only (anthropic-routed) sessions:
|
|
528
|
+
# the plan auto-exit guarantee must hold regardless of provider.
|
|
529
|
+
if active() or bypass_permissions_enabled():
|
|
528
530
|
should_block, reason = should_block_plan_stop(transcript_path)
|
|
529
531
|
if should_block:
|
|
530
532
|
out = {"decision": "block", "reason": reason, "suppressOutput": True}
|
|
@@ -638,6 +640,11 @@ def handle_pre_tool(event: dict[str, Any]) -> None:
|
|
|
638
640
|
return
|
|
639
641
|
|
|
640
642
|
if tool in {"EnterPlanMode", "ExitPlanMode"}:
|
|
643
|
+
# Under bypass, always auto-allow ExitPlanMode before any transcript-based
|
|
644
|
+
# stale check -- the session must escape plan mode without a human, and a
|
|
645
|
+
# truncated transcript must not be able to deny the exit.
|
|
646
|
+
if tool == "ExitPlanMode" and handle_plan_exit_pre_tool(event):
|
|
647
|
+
return
|
|
641
648
|
transcript_path = str(event.get("transcript_path") or "")
|
|
642
649
|
if transcript_path:
|
|
643
650
|
in_plan_mode = transcript_plan_mode_active(transcript_path)
|
|
@@ -721,6 +728,32 @@ def handle_post_failure(event: dict[str, Any]) -> None:
|
|
|
721
728
|
post_failure_context(hint)
|
|
722
729
|
|
|
723
730
|
|
|
731
|
+
def handle_plan_exit_pre_tool(event: dict[str, Any]) -> bool:
|
|
732
|
+
"""Auto-allow ExitPlanMode on PreToolUse for a bypass session.
|
|
733
|
+
|
|
734
|
+
PermissionRequest hooks do not fire in headless ``-p`` mode, so the
|
|
735
|
+
PermissionRequest-based auto-allow (handle_permission_request) never runs
|
|
736
|
+
there and a bypass session would deadlock at plan approval. PreToolUse fires
|
|
737
|
+
in every mode, so allowing ExitPlanMode here covers headless and interactive
|
|
738
|
+
sessions alike. We do NOT consult the transcript: a long plan-mode session
|
|
739
|
+
can push the EnterPlanMode marker out of the 240-line read window, which
|
|
740
|
+
would make a stale-detection check wrongly deny the very ExitPlanMode the
|
|
741
|
+
session needs to escape. Under bypass, exiting plan mode is always safe to
|
|
742
|
+
allow.
|
|
743
|
+
"""
|
|
744
|
+
if not bypass_permissions_enabled():
|
|
745
|
+
return False
|
|
746
|
+
raw = event.get("tool_input")
|
|
747
|
+
updated = raw if isinstance(raw, dict) else {}
|
|
748
|
+
log_event("PreToolUse auto-allowed ExitPlanMode under bypass permissions")
|
|
749
|
+
pre_allow(
|
|
750
|
+
updated,
|
|
751
|
+
"ExitPlanMode auto-allowed because claude-any launched with bypass permissions.",
|
|
752
|
+
"Bypass session must not stall on plan approval; exiting plan mode and continuing.",
|
|
753
|
+
)
|
|
754
|
+
return True
|
|
755
|
+
|
|
756
|
+
|
|
724
757
|
def handle_permission_request(event: dict[str, Any]) -> bool:
|
|
725
758
|
tool = event_tool_name(event)
|
|
726
759
|
if tool != "ExitPlanMode":
|
|
@@ -807,19 +840,34 @@ def main() -> int:
|
|
|
807
840
|
return 0
|
|
808
841
|
name = str(event.get("hook_event_name") or "")
|
|
809
842
|
provider = os.environ.get("CLAUDE_ANY_PROVIDER", "").strip()
|
|
810
|
-
|
|
843
|
+
# Stay active for any session Claude Any launched with bypass permissions,
|
|
844
|
+
# even when the provider is "anthropic" (anthropic-routed mode), which is
|
|
845
|
+
# NOT in NON_NATIVE_PROVIDERS. Such a session runs --permission-mode
|
|
846
|
+
# bypassPermissions and must never stall on a human plan-approval decision,
|
|
847
|
+
# so the guard's plan auto-exit has to run for it. CLAUDE_ANY_BYPASS_PERMISSIONS
|
|
848
|
+
# is set only on Claude-Any-launched sessions (it is popped for direct-native
|
|
849
|
+
# launches), so this never re-activates the guard for a plain native session.
|
|
850
|
+
provider_active = active()
|
|
851
|
+
bypass_active = bypass_permissions_enabled()
|
|
852
|
+
is_active = provider_active or bypass_active
|
|
811
853
|
if not is_active:
|
|
812
854
|
if provider:
|
|
813
855
|
log_event(f"inactive provider={provider}")
|
|
814
856
|
return 0
|
|
815
857
|
|
|
816
|
-
#
|
|
817
|
-
#
|
|
818
|
-
#
|
|
858
|
+
# Bypass-only activation (anthropic-routed: provider not in NON_NATIVE_PROVIDERS
|
|
859
|
+
# but launched with bypassPermissions). Here the guard's ONLY job is to keep
|
|
860
|
+
# the autonomous session from stalling on plan approval. It must NOT normalize
|
|
861
|
+
# or deny other tool calls -- a native Anthropic model emits correct schemas,
|
|
862
|
+
# and rewriting its tool input would be a regression. So when only bypass is
|
|
863
|
+
# active, we handle plan auto-exit (Stop idle-block, ExitPlanMode auto-allow on
|
|
864
|
+
# both PermissionRequest and PreToolUse) and otherwise stay silent.
|
|
865
|
+
plan_only = bypass_active and not provider_active
|
|
866
|
+
|
|
819
867
|
if name == "WorktreeCreate":
|
|
820
|
-
return handle_worktree_create(event)
|
|
868
|
+
return 0 if plan_only else handle_worktree_create(event)
|
|
821
869
|
if name == "WorktreeRemove":
|
|
822
|
-
return handle_worktree_remove(event)
|
|
870
|
+
return 0 if plan_only else handle_worktree_remove(event)
|
|
823
871
|
if name in {"Stop", "SubagentStop"}:
|
|
824
872
|
return handle_stop(event)
|
|
825
873
|
if name == "PermissionRequest":
|
|
@@ -843,6 +891,12 @@ def main() -> int:
|
|
|
843
891
|
raw = event.get("tool_input")
|
|
844
892
|
keys = list(raw.keys()) if isinstance(raw, dict) else []
|
|
845
893
|
log_event(f"PreToolUse seen provider={provider} tool={tool} keys={keys}")
|
|
894
|
+
if plan_only:
|
|
895
|
+
# Native model under bypass: only auto-allow ExitPlanMode, never
|
|
896
|
+
# touch other tool input.
|
|
897
|
+
if tool == "ExitPlanMode":
|
|
898
|
+
handle_plan_exit_pre_tool(event)
|
|
899
|
+
return 0
|
|
846
900
|
handle_pre_tool(event)
|
|
847
901
|
elif name == "PostToolUseFailure":
|
|
848
902
|
handle_post_failure(event)
|