@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 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, store multiple keys for the
372
- same provider and Claude Any will rotate them per upstream request:
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 OPENCODE_API_KEYS="KEY1,KEY2,KEY3"
376
- claude-any --ca-provider opencode --ca-api-keys-env OPENCODE_API_KEYS --ca-no-launch
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
@@ -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
- if active():
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
- is_active = active()
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
- # Claude Any hooks are installed in Claude Code's global settings, so they
817
- # must be silent for native Claude sessions. Only alter worktree, stop, and
818
- # tool behavior when Claude Any launched the process with an active provider.
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)