@oneciel-ai/claude-any 0.1.82 → 0.1.83

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.
Files changed (2) hide show
  1. package/claude_any.py +55 -9
  2. package/package.json +1 -1
package/claude_any.py CHANGED
@@ -104,7 +104,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
104
104
  "fakechat": "plugin:fakechat@claude-plugins-official",
105
105
  }
106
106
  APP_NAME = "Claude Any"
107
- VERSION = "0.1.82"
107
+ VERSION = "0.1.83"
108
108
  CREDITS = "Credits: One Ciel LLC"
109
109
 
110
110
  LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
@@ -13749,14 +13749,52 @@ def _write_fd_all(fd: int, data: bytes) -> None:
13749
13749
  view = view[written:]
13750
13750
 
13751
13751
 
13752
- def _channel_wake_input_bytes(prompt: str) -> bytes:
13752
+ def _channel_wake_enter_bytes(value: str | bytes | None = None) -> bytes:
13753
+ raw: str | bytes | None = value
13754
+ if raw is None:
13755
+ raw = os.environ.get("CLAUDE_ANY_CHANNEL_WAKE_ENTER")
13756
+ if isinstance(raw, bytes):
13757
+ return raw if raw in (b"\n", b"\r", b"\r\n") else b"\n"
13758
+ normalized = str(raw or "").strip().lower()
13759
+ if normalized in {"", "lf", "nl", "newline", "linefeed", "\\n"}:
13760
+ return b"\n"
13761
+ if normalized in {"cr", "return", "carriage-return", "carriage_return", "\\r"}:
13762
+ return b"\r"
13763
+ if normalized in {"crlf", "cr-lf", "return-newline", "\\r\\n"}:
13764
+ return b"\r\n"
13765
+ return b"\n"
13766
+
13767
+
13768
+ def _channel_enter_bytes_from_user_input(data: bytes) -> bytes | None:
13769
+ if not data:
13770
+ return None
13771
+ if data in (b"\n", b"\r", b"\r\n"):
13772
+ return data
13773
+ last_lf = data.rfind(b"\n")
13774
+ last_cr = data.rfind(b"\r")
13775
+ if last_lf < 0 and last_cr < 0:
13776
+ return None
13777
+ if last_lf > last_cr:
13778
+ return b"\r\n" if last_lf > 0 and data[last_lf - 1 : last_lf] == b"\r" else b"\n"
13779
+ return b"\r\n" if last_cr + 1 < len(data) and data[last_cr + 1 : last_cr + 2] == b"\n" else b"\r"
13780
+
13781
+
13782
+ def _channel_enter_label(enter_bytes: bytes) -> str:
13783
+ if enter_bytes == b"\r":
13784
+ return "cr"
13785
+ if enter_bytes == b"\r\n":
13786
+ return "crlf"
13787
+ return "lf"
13788
+
13789
+
13790
+ def _channel_wake_input_bytes(prompt: str, enter_bytes: bytes | None = None) -> bytes:
13753
13791
  # Ctrl-U clears any stale line editor text before submitting the synthetic prompt.
13754
- # Claude Code's interactive input is terminal-driven. In the PTY proxy path,
13755
- # observed Enter keypresses arrive as LF, so submit with LF rather than CR.
13756
- return b"\x15" + prompt.encode("utf-8", errors="replace") + b"\n"
13792
+ # Claude Code's interactive input is terminal-driven; the submit byte can
13793
+ # differ by PTY/input stack, so callers pass the observed Enter sequence.
13794
+ return b"\x15" + prompt.encode("utf-8", errors="replace") + _channel_wake_enter_bytes(enter_bytes)
13757
13795
 
13758
13796
 
13759
- def _inject_pending_channel_messages(master_fd: int, last_id: int) -> int:
13797
+ def _inject_pending_channel_messages(master_fd: int, last_id: int, enter_bytes: bytes | None = None) -> int:
13760
13798
  pending: list[dict[str, Any]] = []
13761
13799
  for message in read_chat_messages(last_id, None, None, 100):
13762
13800
  try:
@@ -13773,10 +13811,14 @@ def _inject_pending_channel_messages(master_fd: int, last_id: int) -> int:
13773
13811
  pending.append(message)
13774
13812
  if pending:
13775
13813
  prompt = format_channel_wake_batch_prompt(pending)
13776
- _write_fd_all(master_fd, _channel_wake_input_bytes(prompt))
13814
+ submit_bytes = _channel_wake_enter_bytes(enter_bytes)
13815
+ _write_fd_all(master_fd, _channel_wake_input_bytes(prompt, submit_bytes))
13777
13816
  ids = ",".join(str(message.get("id") or "") for message in pending)
13778
13817
  channels = ",".join(sorted({str(message.get("channel") or "default") for message in pending}))
13779
- router_log("INFO", f"channel_stdin_proxy_injected count={len(pending)} message_ids={ids} channels={channels}")
13818
+ router_log(
13819
+ "INFO",
13820
+ f"channel_stdin_proxy_injected count={len(pending)} message_ids={ids} channels={channels} enter={_channel_enter_label(submit_bytes)}",
13821
+ )
13780
13822
  return last_id
13781
13823
 
13782
13824
 
@@ -13806,6 +13848,7 @@ def subprocess_call_with_channel_wake_proxy(cmd: list[str], env: dict[str, str])
13806
13848
  stdout_fd = sys.stdout.fileno()
13807
13849
  old_attrs = termios.tcgetattr(stdin_fd)
13808
13850
  last_channel_poll = 0.0
13851
+ channel_enter_bytes = _channel_wake_enter_bytes()
13809
13852
  try:
13810
13853
  tty.setraw(stdin_fd)
13811
13854
  while proc.poll() is None:
@@ -13816,6 +13859,9 @@ def subprocess_call_with_channel_wake_proxy(cmd: list[str], env: dict[str, str])
13816
13859
  if stdin_fd in readable:
13817
13860
  data = os.read(stdin_fd, 4096)
13818
13861
  if data:
13862
+ observed_enter = _channel_enter_bytes_from_user_input(data)
13863
+ if observed_enter:
13864
+ channel_enter_bytes = observed_enter
13819
13865
  _write_fd_all(master_fd, data)
13820
13866
  if master_fd in readable:
13821
13867
  try:
@@ -13830,7 +13876,7 @@ def subprocess_call_with_channel_wake_proxy(cmd: list[str], env: dict[str, str])
13830
13876
  marker = _chat_messages_file_marker()
13831
13877
  if marker != last_channel_marker:
13832
13878
  last_channel_marker = marker
13833
- last_id = _inject_pending_channel_messages(master_fd, last_id)
13879
+ last_id = _inject_pending_channel_messages(master_fd, last_id, channel_enter_bytes)
13834
13880
  while True:
13835
13881
  try:
13836
13882
  readable, _, _ = select.select([master_fd], [], [], 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/claude-any",
3
- "version": "0.1.82",
3
+ "version": "0.1.83",
4
4
  "description": "Claude Code provider selector for Anthropic, Ollama, Ollama Cloud, vLLM, NVIDIA hosted, and self-hosted NIM.",
5
5
  "license": "MIT",
6
6
  "author": "One Ciel LLC",