driftstone 0.1.0__tar.gz

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 (52) hide show
  1. driftstone-0.1.0/.gitignore +10 -0
  2. driftstone-0.1.0/.python-version +1 -0
  3. driftstone-0.1.0/PKG-INFO +87 -0
  4. driftstone-0.1.0/README.md +68 -0
  5. driftstone-0.1.0/pyproject.toml +36 -0
  6. driftstone-0.1.0/src/driftstone/__init__.py +15 -0
  7. driftstone-0.1.0/src/driftstone/_internal/__init__.py +1 -0
  8. driftstone-0.1.0/src/driftstone/_internal/constants.py +7 -0
  9. driftstone-0.1.0/src/driftstone/_internal/transport.py +103 -0
  10. driftstone-0.1.0/src/driftstone/_internal/utils.py +21 -0
  11. driftstone-0.1.0/src/driftstone/client.py +67 -0
  12. driftstone-0.1.0/src/driftstone/exceptions.py +14 -0
  13. driftstone-0.1.0/src/driftstone/models/__init__.py +1 -0
  14. driftstone-0.1.0/src/driftstone/models/access_group.py +32 -0
  15. driftstone-0.1.0/src/driftstone/models/app.py +120 -0
  16. driftstone-0.1.0/src/driftstone/models/channel.py +123 -0
  17. driftstone-0.1.0/src/driftstone/models/instance.py +51 -0
  18. driftstone-0.1.0/src/driftstone/models/profile.py +61 -0
  19. driftstone-0.1.0/src/driftstone/models/response.py +12 -0
  20. driftstone-0.1.0/src/driftstone/models/session.py +33 -0
  21. driftstone-0.1.0/src/driftstone/models/version.py +39 -0
  22. driftstone-0.1.0/src/driftstone/py.typed +1 -0
  23. driftstone-0.1.0/src/driftstone/resources/__init__.py +1 -0
  24. driftstone-0.1.0/src/driftstone/resources/_base.py +18 -0
  25. driftstone-0.1.0/src/driftstone/resources/access_group.py +125 -0
  26. driftstone-0.1.0/src/driftstone/resources/app.py +124 -0
  27. driftstone-0.1.0/src/driftstone/resources/channel.py +220 -0
  28. driftstone-0.1.0/src/driftstone/resources/instance.py +43 -0
  29. driftstone-0.1.0/src/driftstone/resources/profile.py +59 -0
  30. driftstone-0.1.0/src/driftstone/resources/session.py +101 -0
  31. driftstone-0.1.0/src/driftstone/resources/state.py +163 -0
  32. driftstone-0.1.0/src/driftstone/resources/version.py +163 -0
  33. driftstone-0.1.0/src/driftstone/version.py +1 -0
  34. driftstone-0.1.0/tests/.claude/agent.json +14 -0
  35. driftstone-0.1.0/tests/.claude/environment.json +9 -0
  36. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/LICENSE.txt +202 -0
  37. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/SKILL.md +254 -0
  38. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/core/easing.py +234 -0
  39. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/core/frame_composer.py +176 -0
  40. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/core/gif_builder.py +269 -0
  41. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/core/validators.py +136 -0
  42. driftstone-0.1.0/tests/.claude/skills/slack-gif-creator/requirements.txt +4 -0
  43. driftstone-0.1.0/tests/.openclaw/openclaw.json +94 -0
  44. driftstone-0.1.0/tests/.openclaw/workspace/AGENTS.md +212 -0
  45. driftstone-0.1.0/tests/.openclaw/workspace/BOOTSTRAP.md +55 -0
  46. driftstone-0.1.0/tests/.openclaw/workspace/HEARTBEAT.md +5 -0
  47. driftstone-0.1.0/tests/.openclaw/workspace/IDENTITY.md +23 -0
  48. driftstone-0.1.0/tests/.openclaw/workspace/SOUL.md +36 -0
  49. driftstone-0.1.0/tests/.openclaw/workspace/TOOLS.md +40 -0
  50. driftstone-0.1.0/tests/.openclaw/workspace/USER.md +17 -0
  51. driftstone-0.1.0/tests/quick_start_claude.py +41 -0
  52. driftstone-0.1.0/tests/quick_start_openclaw.py +18 -0
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: driftstone
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Driftstone API.
5
+ Project-URL: Homepage, https://www.driftstone.ai/
6
+ Author: Driftstone
7
+ Keywords: api-client,driftstone,sdk
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: httpx<1.0,>=0.27
18
+ Description-Content-Type: text/markdown
19
+
20
+ ## Driftstone - Python
21
+
22
+ Python SDK for the Driftstone API.
23
+
24
+ ### Install
25
+
26
+ ```bash
27
+ pip install driftstone
28
+ ```
29
+
30
+ ### Quick Start
31
+
32
+ ```python
33
+ from driftstone import Driftstone
34
+
35
+ client = Driftstone(api_key="your-api-key")
36
+
37
+ app = client.app.init("typedef-app",
38
+ type="claude",
39
+ version_path=".openclaw"
40
+ )
41
+
42
+ channel = app.channel.init("my-api",
43
+ type="api"
44
+ )
45
+
46
+ stream = channel.run(
47
+ session_id="my-custom-session-id",
48
+ user_id="stephen@pickaxe.co",
49
+ input="hello world"
50
+ )
51
+
52
+ for event in stream:
53
+ if event.done:
54
+ break
55
+
56
+ print(event)
57
+
58
+ ```
59
+
60
+ ### Using A Context Manager
61
+
62
+ ```python
63
+ from driftstone import Driftstone
64
+
65
+ with Driftstone(api_key="your-api-key") as client:
66
+ app = client.app.init("typedef-app",
67
+ type="claude",
68
+ version_path=".openclaw"
69
+ )
70
+
71
+ channel = app.channel.init("my-api",
72
+ type="api"
73
+ )
74
+
75
+ stream = channel.run(
76
+ session_id="my-custom-session-id",
77
+ user_id="stephen@pickaxe.co",
78
+ input="hello world"
79
+ )
80
+
81
+ for event in stream:
82
+ if event.done:
83
+ break
84
+
85
+ print(event)
86
+
87
+ ```
@@ -0,0 +1,68 @@
1
+ ## Driftstone - Python
2
+
3
+ Python SDK for the Driftstone API.
4
+
5
+ ### Install
6
+
7
+ ```bash
8
+ pip install driftstone
9
+ ```
10
+
11
+ ### Quick Start
12
+
13
+ ```python
14
+ from driftstone import Driftstone
15
+
16
+ client = Driftstone(api_key="your-api-key")
17
+
18
+ app = client.app.init("typedef-app",
19
+ type="claude",
20
+ version_path=".openclaw"
21
+ )
22
+
23
+ channel = app.channel.init("my-api",
24
+ type="api"
25
+ )
26
+
27
+ stream = channel.run(
28
+ session_id="my-custom-session-id",
29
+ user_id="stephen@pickaxe.co",
30
+ input="hello world"
31
+ )
32
+
33
+ for event in stream:
34
+ if event.done:
35
+ break
36
+
37
+ print(event)
38
+
39
+ ```
40
+
41
+ ### Using A Context Manager
42
+
43
+ ```python
44
+ from driftstone import Driftstone
45
+
46
+ with Driftstone(api_key="your-api-key") as client:
47
+ app = client.app.init("typedef-app",
48
+ type="claude",
49
+ version_path=".openclaw"
50
+ )
51
+
52
+ channel = app.channel.init("my-api",
53
+ type="api"
54
+ )
55
+
56
+ stream = channel.run(
57
+ session_id="my-custom-session-id",
58
+ user_id="stephen@pickaxe.co",
59
+ input="hello world"
60
+ )
61
+
62
+ for event in stream:
63
+ if event.done:
64
+ break
65
+
66
+ print(event)
67
+
68
+ ```
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "driftstone"
7
+ dynamic = ["version"]
8
+ description = "Python SDK for the Driftstone API."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "httpx>=0.27,<1.0",
13
+ ]
14
+ authors = [
15
+ { name = "Driftstone" },
16
+ ]
17
+ keywords = ["driftstone", "sdk", "api-client"]
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://www.driftstone.ai/"
31
+
32
+ [tool.hatch.version]
33
+ path = "src/driftstone/version.py"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/driftstone"]
@@ -0,0 +1,15 @@
1
+ from .client import Driftstone
2
+ from .exceptions import (
3
+ DriftstoneAPIError,
4
+ DriftstoneError,
5
+ DriftstoneHTTPError,
6
+ DriftstoneResponseError,
7
+ )
8
+
9
+ __all__ = [
10
+ "Driftstone",
11
+ "DriftstoneError",
12
+ "DriftstoneHTTPError",
13
+ "DriftstoneResponseError",
14
+ "DriftstoneAPIError",
15
+ ]
@@ -0,0 +1 @@
1
+ """Internal Driftstone SDK implementation details."""
@@ -0,0 +1,7 @@
1
+ DEFAULT_API_BASE_URL = "https://pickaxeproject--driftstone-api-api.modal.run"
2
+
3
+ GCS_BUCKET_NAME = "driftstone"
4
+
5
+ AVAILABLE_APP_TYPES = ("claude", "openclaw")
6
+
7
+ AVAILABLE_CHANNEL_TYPES = ("api", "imessage")
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal, cast
4
+ import httpx
5
+
6
+ from .utils import logger
7
+ from ..exceptions import (
8
+ DriftstoneAPIError,
9
+ DriftstoneHTTPError,
10
+ DriftstoneResponseError
11
+ )
12
+
13
+ Method = Literal["GET", "POST", "PUT", "PATCH", "DELETE"]
14
+
15
+ class Transport:
16
+ def __init__(
17
+ self,
18
+ *,
19
+ base_url: str,
20
+ api_key: str,
21
+ timeout: float,
22
+ user_agent: str,
23
+ http_client: httpx.Client | None = None,
24
+ ) -> None:
25
+ self.timeout = timeout
26
+ self._headers = {
27
+ "Authorization": f"Bearer {api_key}",
28
+ "Content-Type": "application/json",
29
+ "Accept": "application/json",
30
+ "User-Agent": user_agent,
31
+ }
32
+ self._owns_http_client = http_client is None
33
+ self._http = http_client or httpx.Client(
34
+ base_url=base_url,
35
+ timeout=timeout,
36
+ headers=self._headers,
37
+ )
38
+
39
+ @property
40
+ def headers(self) -> dict[str, str]:
41
+ return self._headers.copy()
42
+
43
+ def close(self) -> None:
44
+ if self._owns_http_client:
45
+ self._http.close()
46
+
47
+ @staticmethod
48
+ def _parse_response(response: httpx.Response) -> dict[str, Any]:
49
+ try:
50
+ decoded = response.json()
51
+ except ValueError as exc:
52
+ raise DriftstoneResponseError(
53
+ f"Driftstone API returned non-JSON response (status={response.status_code})"
54
+ ) from exc
55
+
56
+ if not isinstance(decoded, dict):
57
+ raise DriftstoneResponseError(
58
+ "Driftstone API returned an unexpected response payload"
59
+ )
60
+
61
+ return cast(dict[str, Any], decoded)
62
+
63
+ def request(
64
+ self,
65
+ method: Method,
66
+ path: str,
67
+ payload: dict[str, Any] | None = None,
68
+ ) -> dict[str, Any]:
69
+ payload = payload or {}
70
+
71
+ try:
72
+ kwargs: dict[str, Any] = {
73
+ "method": method,
74
+ "url": path,
75
+ "headers": self._headers,
76
+ }
77
+ if method == "GET":
78
+ kwargs["params"] = payload
79
+ else:
80
+ kwargs["json"] = payload
81
+
82
+ response = self._http.request(**kwargs)
83
+ except httpx.HTTPError as exc:
84
+ raise DriftstoneHTTPError(f"HTTP request failed: {exc}") from exc
85
+
86
+ data = self._parse_response(response)
87
+
88
+ logger.debug(
89
+ "Received response from Driftstone API (status=%d, data=%s)",
90
+ response.status_code,
91
+ data,
92
+ )
93
+
94
+ if response.status_code >= 400:
95
+ raise DriftstoneAPIError(
96
+ f"Driftstone API returned error status {response.status_code}: {data}"
97
+ )
98
+
99
+ if data.get("success") is False:
100
+ message = str(data.get("error") or data.get("message") or "Request failed")
101
+ raise DriftstoneAPIError(f"Driftstone API error: {message}")
102
+
103
+ return data
@@ -0,0 +1,21 @@
1
+ import logging
2
+ import json
3
+
4
+ logger = logging.getLogger("driftstone")
5
+ logger.setLevel(logging.WARNING)
6
+ logger.addHandler(logging.NullHandler())
7
+
8
+ logging.basicConfig(
9
+ format="[%(funcName)s][%(asctime)s]: %(levelname)s %(message)s",
10
+ datefmt="%m/%d/%Y %H:%M:%S",
11
+ )
12
+
13
+ def set_debug(enabled: bool) -> None:
14
+ logger.setLevel(logging.INFO if enabled else logging.WARNING)
15
+
16
+ def safe_parse_json(raw: str):
17
+ try:
18
+ return json.loads(raw)
19
+ except Exception as e:
20
+ logger.error("Failed to parse JSON: %s", e)
21
+ return None
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal
4
+
5
+ import httpx
6
+
7
+ from ._internal.constants import DEFAULT_API_BASE_URL
8
+ from ._internal.utils import logger, set_debug
9
+ from ._internal.transport import Method, Transport
10
+ from .resources.app import DriftstoneApp
11
+ from .version import __version__
12
+
13
+ class Driftstone:
14
+ def __init__(
15
+ self,
16
+ api_key: str,
17
+ *,
18
+ version: Literal["v1"] = "v1",
19
+ timeout: float = 30.0,
20
+ debug: bool = False,
21
+ http_client: httpx.Client | None = None,
22
+ ) -> None:
23
+ self.debug = debug
24
+ set_debug(self.debug)
25
+
26
+ logger.info("Initializing Driftstone client with provided API key")
27
+
28
+ trimmed_key = api_key.strip()
29
+ if not trimmed_key:
30
+ raise ValueError("api_key must be provided")
31
+ if not trimmed_key.startswith("dk-"):
32
+ raise ValueError("Invalid Driftstone API key")
33
+
34
+ self.api_key = trimmed_key
35
+ self.version = version
36
+ self.timeout = timeout
37
+ self.base_url = f"{DEFAULT_API_BASE_URL}/{self.version}"
38
+
39
+ self._transport = Transport(
40
+ base_url=self.base_url,
41
+ api_key=self.api_key,
42
+ timeout=self.timeout,
43
+ user_agent=f"Driftstone-python/{__version__}",
44
+ http_client=http_client,
45
+ )
46
+
47
+ def close(self) -> None:
48
+ self._transport.close()
49
+
50
+ def __enter__(self) -> "Driftstone":
51
+ return self
52
+
53
+ def __exit__(self, *_: object) -> None:
54
+ self.close()
55
+
56
+ def _request(
57
+ self,
58
+ method: Method,
59
+ path: str,
60
+ payload: dict[str, Any] | None = None,
61
+ ) -> dict[str, Any]:
62
+ return self._transport.request(method, path, payload)
63
+
64
+ @property
65
+ def app(self) -> DriftstoneApp:
66
+ return DriftstoneApp(self)
67
+
@@ -0,0 +1,14 @@
1
+ class DriftstoneError(Exception):
2
+ """Base exception for the Driftstone SDK."""
3
+
4
+
5
+ class DriftstoneHTTPError(DriftstoneError):
6
+ """Raised when the underlying HTTP request fails."""
7
+
8
+
9
+ class DriftstoneResponseError(DriftstoneError):
10
+ """Raised when an API response cannot be parsed as expected."""
11
+
12
+
13
+ class DriftstoneAPIError(DriftstoneError):
14
+ """Raised when the Driftstone API returns an error."""
@@ -0,0 +1 @@
1
+ """Driftstone SDK models."""
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Any, Literal, Mapping
6
+
7
+ AccessGroupLimitReset = Literal["daily", "monthly", "yearly"]
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class DriftstoneAccessGroupModel:
12
+ id: str
13
+ appId: str
14
+ name: str
15
+ limit: int
16
+ limitReset: AccessGroupLimitReset
17
+ limitMessage: str
18
+ createdAt: datetime
19
+ updatedAt: datetime
20
+
21
+ @classmethod
22
+ def _from_api(cls, payload: Mapping[str, Any]) -> "DriftstoneAccessGroupModel":
23
+ return cls(
24
+ id=payload["id"],
25
+ appId=payload["appId"],
26
+ name=payload["name"],
27
+ limit=payload["limit"],
28
+ limitReset=payload["limitReset"],
29
+ limitMessage=payload["limitMessage"],
30
+ createdAt=datetime.fromisoformat(payload["createdAt"]),
31
+ updatedAt=datetime.fromisoformat(payload["updatedAt"]),
32
+ )
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Literal,
9
+ Mapping,
10
+ Optional,
11
+ TypedDict,
12
+ Union,
13
+ Required,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from ..resources.access_group import DriftstoneAccessGroup
18
+ from ..resources.version import DriftstoneVersion
19
+ from ..resources.channel import DriftstoneChannel
20
+ from ..resources.session import DriftstoneSession
21
+ from ..resources.profile import DriftstoneProfile
22
+ from ..resources.instance import DriftstoneInstance
23
+
24
+ ApplicationType = Literal["claude", "openclaw"]
25
+
26
+ class AppInitLimit(TypedDict):
27
+ name: Optional[str]
28
+ amount: Required[int]
29
+ interval: Required[Literal["daily", "weekly", "monthly"]]
30
+ message: Optional[str]
31
+
32
+ class AppInitCredentialMCPOAuthNoneParam(TypedDict):
33
+ type: Required[Literal["none"]]
34
+
35
+ class AppInitCredentialMCPOAuthBasicParam(TypedDict):
36
+ type: Required[Literal["client_secret_basic"]]
37
+ client_secret: Required[str]
38
+
39
+ class AppInitCredentialMCPOAuthPostParam(TypedDict):
40
+ type: Required[Literal["client_secret_post"]]
41
+ client_secret: Required[str]
42
+
43
+ class AppInitCredentialMCPOAuthRefresh(TypedDict):
44
+ client_id: Required[str]
45
+ refresh_token: Required[str]
46
+ token_endpoint: Required[str]
47
+ token_endpoint_auth: Required[Union[
48
+ AppInitCredentialMCPOAuthNoneParam,
49
+ AppInitCredentialMCPOAuthBasicParam,
50
+ AppInitCredentialMCPOAuthPostParam,
51
+ ]]
52
+ resource: Optional[str]
53
+ scope: Optional[str]
54
+
55
+ class AppInitCredentialMCPOAuth(TypedDict):
56
+ access_token: Required[str]
57
+ mcp_server_url: Required[str]
58
+ type: Required[Literal["mcp_oauth"]]
59
+ expires_at: Optional[datetime]
60
+ refresh: Optional[AppInitCredentialMCPOAuthRefresh]
61
+
62
+ class AppInitCredentialMCPStaticBearer(TypedDict):
63
+ token: Required[str]
64
+ mcp_server_url: Required[str]
65
+ type: Required[Literal["mcp_oauth"]]
66
+
67
+ AppInitCredential = Union[AppInitCredentialMCPOAuth, AppInitCredentialMCPStaticBearer]
68
+
69
+ @dataclass(frozen=True, slots=True)
70
+ class DriftstoneAppModel:
71
+ id: str
72
+ orgId: str
73
+ type: ApplicationType
74
+ name: str
75
+ slug: str
76
+ vaultId: str
77
+ defaultAccessGroupId: str
78
+ headVersionId: Optional[str]
79
+ metadata: Optional[dict[str, str]]
80
+ createdAt: datetime
81
+ updatedAt: datetime
82
+
83
+ version: DriftstoneVersion
84
+ channel: DriftstoneChannel
85
+ session: DriftstoneSession
86
+ access_group: DriftstoneAccessGroup
87
+ profile: DriftstoneProfile
88
+ instance: DriftstoneInstance
89
+
90
+ @classmethod
91
+ def _from_api(
92
+ cls,
93
+ payload: Mapping[str, Any],
94
+ version: DriftstoneVersion,
95
+ channel: DriftstoneChannel,
96
+ session: DriftstoneSession,
97
+ access_group: DriftstoneAccessGroup,
98
+ profile: DriftstoneProfile,
99
+ instance: DriftstoneInstance,
100
+ ) -> "DriftstoneAppModel":
101
+ return cls(
102
+ id=payload["id"],
103
+ orgId=payload["orgId"],
104
+ type=payload["type"],
105
+ name=payload["name"],
106
+ slug=payload["slug"],
107
+ vaultId=payload["vaultId"],
108
+ defaultAccessGroupId=payload["defaultAccessGroupId"],
109
+ headVersionId=payload.get("headVersionId"),
110
+ metadata=payload.get("metadata"),
111
+ createdAt=datetime.fromisoformat(payload["createdAt"]),
112
+ updatedAt=datetime.fromisoformat(payload["updatedAt"]),
113
+
114
+ version=version,
115
+ channel=channel,
116
+ session=session,
117
+ access_group=access_group,
118
+ profile=profile,
119
+ instance=instance,
120
+ )