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.
- anywhere_cli-0.1.0.dist-info/METADATA +110 -0
- anywhere_cli-0.1.0.dist-info/RECORD +36 -0
- anywhere_cli-0.1.0.dist-info/WHEEL +4 -0
- anywhere_cli-0.1.0.dist-info/entry_points.txt +3 -0
- connector/__init__.py +3 -0
- connector/adapter.py +39 -0
- connector/attachments.py +36 -0
- connector/capabilities.py +334 -0
- connector/claude/__init__.py +8 -0
- connector/claude/history_adapter.py +642 -0
- connector/claude/normalized.py +23 -0
- connector/claude/normalizers.py +97 -0
- connector/claude/path_utils.py +13 -0
- connector/claude/preferences.py +38 -0
- connector/claude/sdk_adapter.py +1377 -0
- connector/claude/timeline_identity.py +47 -0
- connector/claude/timeline_reducer.py +379 -0
- connector/claude/trust.py +69 -0
- connector/cli.py +149 -0
- connector/codex/__init__.py +3 -0
- connector/codex/adapter.py +951 -0
- connector/codex/history.py +199 -0
- connector/codex/reducer.py +1223 -0
- connector/codex/rpc.py +260 -0
- connector/launch.py +104 -0
- connector/local/__init__.py +6 -0
- connector/local/common.py +118 -0
- connector/local/file_ops.py +122 -0
- connector/local/ops.py +83 -0
- connector/local/shell.py +225 -0
- connector/local/terminal.py +389 -0
- connector/local_ops.py +5 -0
- connector/protocol.py +26 -0
- connector/runtime.py +1002 -0
- connector/sync_state.py +155 -0
- connector/time.py +7 -0
|
@@ -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
|
+
}
|