clawdi 0.13.0 → 0.13.2

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,29 @@
1
+ ---
2
+ name: clawdi
3
+ description: "Find and read the user's past agent sessions across Claude Code, Codex, OpenClaw, and Hermes; use connected-service tools such as Gmail, GitHub, Notion, Drive, and Calendar; and read Clawdi share URLs (https://cloud.clawdi.ai/s/...) the user pastes."
4
+ ---
5
+
6
+ # Clawdi Cloud
7
+
8
+ Use Clawdi Cloud tools through the `clawdi` MCP server.
9
+
10
+ ## Sessions
11
+
12
+ - Use `session_search` to find past agent conversations by keyword and obtain session UUIDs.
13
+ - Use `session_read` to read a session by UUID or Clawdi share URL.
14
+
15
+ Call `session_read` when the user provides a Clawdi share URL. When the user refers to a
16
+ past conversation without a UUID, call `session_search` first and then read the matching
17
+ session. Do not use a generic web fetcher for Clawdi share URLs.
18
+
19
+ ## Connectors
20
+
21
+ Connected-service tools are registered dynamically from the user's Clawdi Cloud account.
22
+ They can include Gmail, GitHub, Notion, Drive, Calendar, and other services.
23
+
24
+ - Treat the tools as already authenticated for this runtime.
25
+ - If a call reports that no account is connected, ask the user to connect that service in
26
+ the Clawdi Cloud dashboard.
27
+ - Download files returned as signed URLs before processing them.
28
+ - Confirm with the user before side-effecting actions such as sending mail or creating an
29
+ issue.
@@ -12,7 +12,7 @@ import os
12
12
  import re
13
13
  from pathlib import Path
14
14
  from typing import Any
15
- from urllib.parse import parse_qs, quote, unquote, urlsplit
15
+ from urllib.parse import parse_qs, urlsplit
16
16
 
17
17
  try: # pragma: no cover - exercised only under mitmdump.
18
18
  from mitmproxy import ctx, http
@@ -114,6 +114,7 @@ class ClawdiEgressAddon:
114
114
  self.secret_file_path = Path(secret_file) if secret_file else None
115
115
  self.profiles = load_profiles(self.profile_bundle_path)
116
116
  self.secrets = load_secrets(self.secret_file_path)
117
+ validate_profile_secrets(self.profiles, self.secrets)
117
118
  self.profile_hosts = profile_host_set(self.profiles)
118
119
 
119
120
  def should_intercept_sni(self, server_name: str) -> bool:
@@ -205,6 +206,30 @@ def load_secrets(path: Path | None) -> dict[str, str]:
205
206
  return {str(key): str(value) for key, value in raw.items() if isinstance(value, str)}
206
207
 
207
208
 
209
+ def validate_profile_secrets(
210
+ profiles: list[dict[str, Any]], secrets: dict[str, str]
211
+ ) -> None:
212
+ required: set[str] = set()
213
+ for profile in profiles:
214
+ collect_secret_refs(profile, required)
215
+ missing = sorted(ref for ref in required if not secrets.get(ref))
216
+ if missing:
217
+ raise ConfigError(f"egress profile secrets are missing: {', '.join(missing)}")
218
+
219
+
220
+ def collect_secret_refs(value: Any, refs: set[str]) -> None:
221
+ if isinstance(value, list):
222
+ for item in value:
223
+ collect_secret_refs(item, refs)
224
+ return
225
+ if not isinstance(value, dict):
226
+ return
227
+ for key, entry in value.items():
228
+ if isinstance(entry, str) and (key == "secretRef" or key.endswith("SecretRef")):
229
+ refs.add(entry)
230
+ collect_secret_refs(entry, refs)
231
+
232
+
208
233
  def profile_host_set(profiles: list[dict[str, Any]]) -> set[str]:
209
234
  hosts: set[str] = set()
210
235
  for profile in profiles:
@@ -297,9 +322,13 @@ def host_matches(flow: Any, profile: dict[str, Any]) -> bool:
297
322
  match = profile.get("match")
298
323
  if not isinstance(match, dict):
299
324
  return False
300
- expected = normalize_host(str(match.get("host", "")))
325
+ expected_authority = str(match.get("host", "")).strip().lower()
326
+ expected_host, expected_port = split_authority(expected_authority)
327
+ expected = normalize_host(expected_host)
301
328
  if not expected:
302
329
  return False
330
+ if expected_port is not None and getattr(flow.request, "port", None) != expected_port:
331
+ return False
303
332
  return expected in request_host_candidates(flow)
304
333
 
305
334
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdi",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "iCloud for AI Agents — cross-agent sessions, skills, memory, and vault.",
5
5
  "license": "MIT",
6
6
  "type": "module",