anywhere-cli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from connector.claude.normalized import NormalizedClaudeEvent
6
+
7
+
8
+ class ClaudeLiveNormalizer:
9
+ def normalize(self, raw_events: list[dict[str, Any]]) -> list[NormalizedClaudeEvent]:
10
+ return [_event for raw in raw_events for _event in _normalize_raw(raw)]
11
+
12
+
13
+ class ClaudeTranscriptNormalizer:
14
+ def normalize(self, raw_entries: list[dict[str, Any]]) -> list[NormalizedClaudeEvent]:
15
+ return [_event for raw in raw_entries for _event in _normalize_raw(raw)]
16
+
17
+
18
+ def _normalize_raw(raw: dict[str, Any]) -> list[NormalizedClaudeEvent]:
19
+ message = raw.get("message") if isinstance(raw.get("message"), dict) else raw
20
+ claude_session_id = _string(raw.get("session_id") or raw.get("sessionId") or raw.get("uuid")) or "unknown"
21
+ source_event_id = _string(raw.get("uuid") or raw.get("id") or message.get("id")) or "unknown"
22
+ message_id = _string(message.get("id") or raw.get("message_id") or raw.get("messageId"))
23
+ role = _string(message.get("role") or raw.get("role"))
24
+ timestamp = _string(raw.get("timestamp") or message.get("timestamp"))
25
+ content = message.get("content")
26
+ if not isinstance(content, list):
27
+ if isinstance(content, str):
28
+ return [
29
+ NormalizedClaudeEvent(
30
+ claudeSessionId=claude_session_id,
31
+ sourceEventId=source_event_id,
32
+ messageId=message_id,
33
+ role=role if role in {"user", "assistant", "tool", "system"} else None,
34
+ blockIndex=0,
35
+ blockType="text",
36
+ text=content,
37
+ timestamp=timestamp,
38
+ )
39
+ ]
40
+ return []
41
+
42
+ normalized: list[NormalizedClaudeEvent] = []
43
+ for index, block in enumerate(content):
44
+ if not isinstance(block, dict):
45
+ continue
46
+ block_type = _string(block.get("type")) or "unknown"
47
+ if block_type == "text":
48
+ text = _string(block.get("text"))
49
+ if text is not None and text.strip():
50
+ normalized.append(
51
+ NormalizedClaudeEvent(
52
+ claudeSessionId=claude_session_id,
53
+ sourceEventId=f"{source_event_id}:{index}",
54
+ messageId=message_id,
55
+ role=role if role in {"user", "assistant", "tool", "system"} else None,
56
+ blockIndex=index,
57
+ blockType=block_type,
58
+ text=text,
59
+ timestamp=timestamp,
60
+ )
61
+ )
62
+ elif block_type == "tool_use":
63
+ normalized.append(
64
+ NormalizedClaudeEvent(
65
+ claudeSessionId=claude_session_id,
66
+ sourceEventId=f"{source_event_id}:{index}",
67
+ messageId=message_id,
68
+ role="assistant",
69
+ blockIndex=index,
70
+ blockType=block_type,
71
+ toolUseId=_string(block.get("id")),
72
+ toolName=_string(block.get("name")),
73
+ toolInput=block.get("input"),
74
+ timestamp=timestamp,
75
+ )
76
+ )
77
+ elif block_type == "tool_result":
78
+ normalized.append(
79
+ NormalizedClaudeEvent(
80
+ claudeSessionId=claude_session_id,
81
+ sourceEventId=f"{source_event_id}:{index}",
82
+ messageId=message_id,
83
+ role="tool",
84
+ blockIndex=index,
85
+ blockType=block_type,
86
+ toolUseId=_string(block.get("tool_use_id")),
87
+ toolResult=block.get("content"),
88
+ toolResultIsError=block.get("is_error") if isinstance(block.get("is_error"), bool) else None,
89
+ text=_string(block.get("content")),
90
+ timestamp=timestamp,
91
+ )
92
+ )
93
+ return normalized
94
+
95
+
96
+ def _string(value: Any) -> str | None:
97
+ return value if isinstance(value, str) else None
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+
5
+
6
+ def stable_claude_session_id(connector_id: str, claude_uuid: str) -> str:
7
+ """Deterministic session_id derived from (connector, claude session uuid).
8
+
9
+ Mirrors connector.codex.adapter.stable_session_id so the backend never
10
+ sees two ids referring to the same upstream session.
11
+ """
12
+ digest = hashlib.sha256(f"{connector_id}:claude:{claude_uuid}".encode("utf-8")).hexdigest()[:24]
13
+ return f"sess_claude_{digest}"
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from connector.time import utc_now
8
+
9
+
10
+ def _settings_path() -> Path:
11
+ return Path.home() / ".claude" / "settings.json"
12
+
13
+
14
+ def read_local_preferences(path: Path | None = None) -> dict[str, Any]:
15
+ """Read the user's ~/.claude/settings.json and surface the fields the
16
+ backend cares about. Returns an empty dict if the file is missing or
17
+ unreadable; missing fields surface as None so the frontend can fall back
18
+ to the seeded `is_default` choice.
19
+
20
+ Shape mirrors what `connector.preferencesUpdated` puts on the wire.
21
+ """
22
+ target = path or _settings_path()
23
+ if not target.exists():
24
+ return {}
25
+ try:
26
+ data = json.loads(target.read_text(encoding="utf-8"))
27
+ except (OSError, json.JSONDecodeError):
28
+ return {}
29
+ if not isinstance(data, dict):
30
+ return {}
31
+ permissions = data.get("permissions")
32
+ permission_mode = permissions.get("defaultMode") if isinstance(permissions, dict) else None
33
+ return {
34
+ "permissionMode": permission_mode if isinstance(permission_mode, str) else None,
35
+ "model": data.get("model") if isinstance(data.get("model"), str) else None,
36
+ "effort": data.get("effort") if isinstance(data.get("effort"), str) else None,
37
+ "readAt": utc_now(),
38
+ }