waken-sdk 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.
- waken_sdk-0.1.0/PKG-INFO +46 -0
- waken_sdk-0.1.0/README.md +32 -0
- waken_sdk-0.1.0/pyproject.toml +21 -0
- waken_sdk-0.1.0/setup.cfg +4 -0
- waken_sdk-0.1.0/waken/__init__.py +227 -0
- waken_sdk-0.1.0/waken_sdk.egg-info/PKG-INFO +46 -0
- waken_sdk-0.1.0/waken_sdk.egg-info/SOURCES.txt +7 -0
- waken_sdk-0.1.0/waken_sdk.egg-info/dependency_links.txt +1 -0
- waken_sdk-0.1.0/waken_sdk.egg-info/top_level.txt +1 -0
waken_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waken-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Waken SDK: persistent AI agents hosted on the cloud, from Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://waken.sh
|
|
7
|
+
Project-URL: Documentation, https://waken.sh/docs/api
|
|
8
|
+
Project-URL: Source, https://github.com/medhychabour/waken
|
|
9
|
+
Keywords: ai,agents,waken,claude-code,codex,sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# waken-sdk
|
|
16
|
+
|
|
17
|
+
Persistent AI agents hosted on the cloud, from Python. Installed as `waken-sdk`, imported as `waken`. A thin client over the [Waken HTTP API](https://waken.sh/docs/api), no dependencies, Python 3.9+.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install waken-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import os
|
|
25
|
+
from waken import Waken
|
|
26
|
+
|
|
27
|
+
waken = Waken(api_key=os.environ["WAKEN_KEY"])
|
|
28
|
+
|
|
29
|
+
agent = waken.agents.create(
|
|
30
|
+
name="bookkeeper",
|
|
31
|
+
model={"provider": "anthropic", "api_key": os.environ["ANTHROPIC_API_KEY"]},
|
|
32
|
+
spend_cap_usd=10,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
reply = waken.agents.message(agent["id"], "Sort my March receipts by vendor.")
|
|
36
|
+
print(reply["text"])
|
|
37
|
+
|
|
38
|
+
cp = waken.checkpoints.take(agent["id"]) # a point you can rewind to
|
|
39
|
+
waken.checkpoints.restore(agent["id"], cp["id"]) # rewind: files, memory, conversation
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Everything the API does is here: `agents` (create, list, get, delete, message, sleep, wake, restart, usage, resize, set_model, set_harness, logs, exec), `checkpoints` (take, list, restore), `files` (list, read, read_text, write, mkdir, move, delete, upload_tar), `env` (list, set), `triggers` (schedules and the agent's incoming webhook), `webhooks` (events on all your agents), `harnesses()`. Any other route: `waken.request(method, path, body)`.
|
|
43
|
+
|
|
44
|
+
Errors raise `WakenError` with `.status` and the API message.
|
|
45
|
+
|
|
46
|
+
Keys are created in the dashboard, Developers. An agent runs on your own model key, passed at creation and never stored by Waken.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# waken-sdk
|
|
2
|
+
|
|
3
|
+
Persistent AI agents hosted on the cloud, from Python. Installed as `waken-sdk`, imported as `waken`. A thin client over the [Waken HTTP API](https://waken.sh/docs/api), no dependencies, Python 3.9+.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install waken-sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import os
|
|
11
|
+
from waken import Waken
|
|
12
|
+
|
|
13
|
+
waken = Waken(api_key=os.environ["WAKEN_KEY"])
|
|
14
|
+
|
|
15
|
+
agent = waken.agents.create(
|
|
16
|
+
name="bookkeeper",
|
|
17
|
+
model={"provider": "anthropic", "api_key": os.environ["ANTHROPIC_API_KEY"]},
|
|
18
|
+
spend_cap_usd=10,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
reply = waken.agents.message(agent["id"], "Sort my March receipts by vendor.")
|
|
22
|
+
print(reply["text"])
|
|
23
|
+
|
|
24
|
+
cp = waken.checkpoints.take(agent["id"]) # a point you can rewind to
|
|
25
|
+
waken.checkpoints.restore(agent["id"], cp["id"]) # rewind: files, memory, conversation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Everything the API does is here: `agents` (create, list, get, delete, message, sleep, wake, restart, usage, resize, set_model, set_harness, logs, exec), `checkpoints` (take, list, restore), `files` (list, read, read_text, write, mkdir, move, delete, upload_tar), `env` (list, set), `triggers` (schedules and the agent's incoming webhook), `webhooks` (events on all your agents), `harnesses()`. Any other route: `waken.request(method, path, body)`.
|
|
29
|
+
|
|
30
|
+
Errors raise `WakenError` with `.status` and the API message.
|
|
31
|
+
|
|
32
|
+
Keys are created in the dashboard, Developers. An agent runs on your own model key, passed at creation and never stored by Waken.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "waken-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Waken SDK: persistent AI agents hosted on the cloud, from Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["ai", "agents", "waken", "claude-code", "codex", "sdk"]
|
|
13
|
+
classifiers = ["Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://waken.sh"
|
|
17
|
+
Documentation = "https://waken.sh/docs/api"
|
|
18
|
+
Source = "https://github.com/medhychabour/waken"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
packages = ["waken"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Waken SDK for Python. A thin, dependency-free client over the HTTP API (https://waken.sh/docs/api).
|
|
2
|
+
|
|
3
|
+
from waken import Waken
|
|
4
|
+
waken = Waken(api_key=os.environ["WAKEN_KEY"])
|
|
5
|
+
agent = waken.agents.create(name="bookkeeper", model={"provider": "anthropic", "api_key": "sk-ant-..."})
|
|
6
|
+
print(waken.agents.message(agent["id"], "Sort my March receipts by vendor.")["text"])
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.parse
|
|
13
|
+
import urllib.request
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
|
|
16
|
+
__all__ = ["Waken", "WakenError"]
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WakenError(Exception):
|
|
21
|
+
def __init__(self, status: int, message: str, body: Any = None):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.status, self.body = status, body
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Waken:
|
|
27
|
+
def __init__(self, api_key: str, base_url: str = "https://api.waken.sh", timeout: float = 60.0):
|
|
28
|
+
if not api_key:
|
|
29
|
+
raise ValueError("api_key is required: create one in the dashboard, Developers")
|
|
30
|
+
self._key, self._base, self._timeout = api_key, base_url.rstrip("/"), timeout
|
|
31
|
+
self.agents = _Agents(self)
|
|
32
|
+
self.checkpoints = _Checkpoints(self)
|
|
33
|
+
self.files = _Files(self)
|
|
34
|
+
self.env = _Env(self)
|
|
35
|
+
self.triggers = _Triggers(self)
|
|
36
|
+
self.webhooks = _Webhooks(self)
|
|
37
|
+
|
|
38
|
+
# Low level: any route. body may be a dict (sent as JSON), str or bytes (sent as is), or None.
|
|
39
|
+
def request(self, method: str, path: str, body: Any = None, timeout: Optional[float] = None, raw: bool = False, content_type: Optional[str] = None) -> Any:
|
|
40
|
+
headers = {"Authorization": f"Bearer {self._key}"}
|
|
41
|
+
data = None
|
|
42
|
+
if body is not None:
|
|
43
|
+
if isinstance(body, (str, bytes)):
|
|
44
|
+
data = body.encode() if isinstance(body, str) else body
|
|
45
|
+
headers["Content-Type"] = content_type or "application/octet-stream"
|
|
46
|
+
else:
|
|
47
|
+
data = json.dumps(body).encode()
|
|
48
|
+
headers["Content-Type"] = "application/json"
|
|
49
|
+
req = urllib.request.Request(self._base + path, data=data, method=method, headers=headers)
|
|
50
|
+
try:
|
|
51
|
+
with urllib.request.urlopen(req, timeout=timeout or self._timeout) as r:
|
|
52
|
+
payload = r.read()
|
|
53
|
+
except urllib.error.HTTPError as e:
|
|
54
|
+
text = e.read().decode(errors="replace")
|
|
55
|
+
try:
|
|
56
|
+
j = json.loads(text)
|
|
57
|
+
msg = j.get("error") if isinstance(j, dict) else None
|
|
58
|
+
except ValueError:
|
|
59
|
+
j, msg = text, None
|
|
60
|
+
raise WakenError(e.code, msg or f"{method} {path}: HTTP {e.code}", j) from None
|
|
61
|
+
if raw:
|
|
62
|
+
return payload
|
|
63
|
+
if not payload:
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
return json.loads(payload)
|
|
67
|
+
except ValueError:
|
|
68
|
+
return {"raw": payload.decode(errors="replace")}
|
|
69
|
+
|
|
70
|
+
def harnesses(self) -> list:
|
|
71
|
+
return self.request("GET", "/harnesses")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _Agents:
|
|
75
|
+
def __init__(self, c: Waken):
|
|
76
|
+
self._c = c
|
|
77
|
+
|
|
78
|
+
def create(self, **agent: Any) -> dict:
|
|
79
|
+
"""name, harness, model={provider, name, api_key}, env, spend_cap_usd, idle_s, external_id, mem_mib, disk_gib,
|
|
80
|
+
desktop, image, command, port, registry_auth, repo, repo_ref, repo_token."""
|
|
81
|
+
return self._c.request("POST", "/agents", agent, timeout=180)
|
|
82
|
+
|
|
83
|
+
def list(self, external_id: Optional[str] = None) -> list:
|
|
84
|
+
q = f"?external_id={urllib.parse.quote(external_id)}" if external_id else ""
|
|
85
|
+
return self._c.request("GET", "/agents" + q)
|
|
86
|
+
|
|
87
|
+
def get(self, id: str) -> dict:
|
|
88
|
+
return self._c.request("GET", f"/agents/{id}")
|
|
89
|
+
|
|
90
|
+
def delete(self, id: str) -> dict:
|
|
91
|
+
return self._c.request("DELETE", f"/agents/{id}")
|
|
92
|
+
|
|
93
|
+
def message(self, id: str, text: str, timeout: float = 600) -> dict:
|
|
94
|
+
"""Talk to the agent. Wakes it if needed; the call stays open until it answers."""
|
|
95
|
+
return self._c.request("POST", f"/agents/{id}/message", {"text": text}, timeout=timeout)
|
|
96
|
+
|
|
97
|
+
def sleep(self, id: str) -> dict:
|
|
98
|
+
return self._c.request("POST", f"/agents/{id}/sleep", timeout=120)
|
|
99
|
+
|
|
100
|
+
def wake(self, id: str) -> dict:
|
|
101
|
+
return self._c.request("POST", f"/agents/{id}/wake", timeout=120)
|
|
102
|
+
|
|
103
|
+
def restart(self, id: str) -> dict:
|
|
104
|
+
return self._c.request("POST", f"/agents/{id}/restart")
|
|
105
|
+
|
|
106
|
+
def usage(self, id: str) -> dict:
|
|
107
|
+
return self._c.request("GET", f"/agents/{id}/usage")
|
|
108
|
+
|
|
109
|
+
def resize(self, id: str, mem_mib: Optional[int] = None, disk_gib: Optional[int] = None) -> dict:
|
|
110
|
+
body = {k: v for k, v in (("mem_mib", mem_mib), ("disk_gib", disk_gib)) if v}
|
|
111
|
+
return self._c.request("PUT", f"/agents/{id}/size", body, timeout=180)
|
|
112
|
+
|
|
113
|
+
def set_model(self, id: str, model: dict) -> dict:
|
|
114
|
+
return self._c.request("PUT", f"/agents/{id}/model", {"model": model})
|
|
115
|
+
|
|
116
|
+
def set_harness(self, id: str, harness: str, model: dict) -> dict:
|
|
117
|
+
return self._c.request("PUT", f"/agents/{id}/harness", {"harness": harness, "model": model}, timeout=180)
|
|
118
|
+
|
|
119
|
+
def logs(self, id: str, lines: int = 100) -> dict:
|
|
120
|
+
return self._c.request("GET", f"/agents/{id}/logs?lines={lines}")
|
|
121
|
+
|
|
122
|
+
def exec(self, id: str, command: str, timeout_s: int = 60) -> dict:
|
|
123
|
+
return self._c.request("POST", f"/agents/{id}/exec", {"command": command, "timeout_s": timeout_s}, timeout=timeout_s + 30)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _Checkpoints:
|
|
127
|
+
def __init__(self, c: Waken):
|
|
128
|
+
self._c = c
|
|
129
|
+
|
|
130
|
+
def take(self, id: str) -> dict:
|
|
131
|
+
return self._c.request("POST", f"/agents/{id}/snapshot", timeout=180)
|
|
132
|
+
|
|
133
|
+
def list(self, id: str) -> list:
|
|
134
|
+
return self._c.request("GET", f"/agents/{id}/snapshots")
|
|
135
|
+
|
|
136
|
+
def restore(self, id: str, snapshot_id: Optional[str] = None) -> dict:
|
|
137
|
+
"""Rewind to a checkpoint, or to the latest one when snapshot_id is omitted."""
|
|
138
|
+
return self._c.request("POST", f"/agents/{id}/restore", {"snapshot_id": snapshot_id} if snapshot_id else {}, timeout=180)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class _Files:
|
|
142
|
+
"""Paths are relative to the agent's working folder."""
|
|
143
|
+
|
|
144
|
+
def __init__(self, c: Waken):
|
|
145
|
+
self._c = c
|
|
146
|
+
|
|
147
|
+
def list(self, id: str, path: str = "") -> Any:
|
|
148
|
+
return self._c.request("GET", f"/agents/{id}/files/list?path={urllib.parse.quote(path)}")
|
|
149
|
+
|
|
150
|
+
def read(self, id: str, path: str) -> bytes:
|
|
151
|
+
return self._c.request("GET", f"/agents/{id}/files/download?path={urllib.parse.quote(path)}", raw=True)
|
|
152
|
+
|
|
153
|
+
def read_text(self, id: str, path: str) -> str:
|
|
154
|
+
return self.read(id, path).decode()
|
|
155
|
+
|
|
156
|
+
def write(self, id: str, path: str, content: Any) -> Any:
|
|
157
|
+
return self._c.request("PUT", f"/agents/{id}/files/write?path={urllib.parse.quote(path)}", content)
|
|
158
|
+
|
|
159
|
+
def mkdir(self, id: str, path: str) -> Any:
|
|
160
|
+
return self._c.request("POST", f"/agents/{id}/files/mkdir", {"path": path})
|
|
161
|
+
|
|
162
|
+
def move(self, id: str, src: str, dst: str) -> Any:
|
|
163
|
+
return self._c.request("POST", f"/agents/{id}/files/move", {"from": src, "to": dst})
|
|
164
|
+
|
|
165
|
+
def delete(self, id: str, path: str) -> Any:
|
|
166
|
+
return self._c.request("DELETE", f"/agents/{id}/files/delete?path={urllib.parse.quote(path)}")
|
|
167
|
+
|
|
168
|
+
def upload_tar(self, id: str, tar_gz: bytes) -> dict:
|
|
169
|
+
"""Upload a whole folder as a gzipped tarball."""
|
|
170
|
+
return self._c.request("POST", f"/agents/{id}/files", tar_gz, timeout=300, content_type="application/gzip")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class _Env:
|
|
174
|
+
"""Environment variables of an agent. Values are never returned."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, c: Waken):
|
|
177
|
+
self._c = c
|
|
178
|
+
|
|
179
|
+
def list(self, id: str) -> dict:
|
|
180
|
+
return self._c.request("GET", f"/agents/{id}/env")
|
|
181
|
+
|
|
182
|
+
def set(self, id: str, env: dict, unset: Optional[list] = None, reload: bool = False) -> Any:
|
|
183
|
+
return self._c.request("PUT", f"/agents/{id}/env", {"env": env, "unset": unset or [], "reload": reload})
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class _Triggers:
|
|
187
|
+
"""Schedules, and the incoming webhook of an agent."""
|
|
188
|
+
|
|
189
|
+
def __init__(self, c: Waken):
|
|
190
|
+
self._c = c
|
|
191
|
+
|
|
192
|
+
def list(self, id: str) -> list:
|
|
193
|
+
return self._c.request("GET", f"/agents/{id}/triggers")
|
|
194
|
+
|
|
195
|
+
def create(self, id: str, cron: str, message: str) -> dict:
|
|
196
|
+
return self._c.request("POST", f"/agents/{id}/triggers", {"cron": cron, "message": message})
|
|
197
|
+
|
|
198
|
+
def delete(self, id: str, tid: str) -> Any:
|
|
199
|
+
return self._c.request("DELETE", f"/agents/{id}/triggers/{tid}")
|
|
200
|
+
|
|
201
|
+
def run(self, id: str, tid: str) -> Any:
|
|
202
|
+
return self._c.request("POST", f"/agents/{id}/triggers/{tid}/run", timeout=600)
|
|
203
|
+
|
|
204
|
+
def webhook(self, id: str) -> dict:
|
|
205
|
+
return self._c.request("GET", f"/agents/{id}/webhook")
|
|
206
|
+
|
|
207
|
+
def rotate_webhook(self, id: str) -> dict:
|
|
208
|
+
return self._c.request("POST", f"/agents/{id}/webhook/rotate")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class _Webhooks:
|
|
212
|
+
"""Outgoing webhooks: events on all your agents."""
|
|
213
|
+
|
|
214
|
+
def __init__(self, c: Waken):
|
|
215
|
+
self._c = c
|
|
216
|
+
|
|
217
|
+
def list(self) -> list:
|
|
218
|
+
return self._c.request("GET", "/webhooks")
|
|
219
|
+
|
|
220
|
+
def create(self, url: str, events: Optional[list] = None) -> dict:
|
|
221
|
+
body: dict = {"url": url}
|
|
222
|
+
if events:
|
|
223
|
+
body["events"] = events
|
|
224
|
+
return self._c.request("POST", "/webhooks", body)
|
|
225
|
+
|
|
226
|
+
def delete(self, wid: str) -> Any:
|
|
227
|
+
return self._c.request("DELETE", f"/webhooks/{wid}")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: waken-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Waken SDK: persistent AI agents hosted on the cloud, from Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://waken.sh
|
|
7
|
+
Project-URL: Documentation, https://waken.sh/docs/api
|
|
8
|
+
Project-URL: Source, https://github.com/medhychabour/waken
|
|
9
|
+
Keywords: ai,agents,waken,claude-code,codex,sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# waken-sdk
|
|
16
|
+
|
|
17
|
+
Persistent AI agents hosted on the cloud, from Python. Installed as `waken-sdk`, imported as `waken`. A thin client over the [Waken HTTP API](https://waken.sh/docs/api), no dependencies, Python 3.9+.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install waken-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import os
|
|
25
|
+
from waken import Waken
|
|
26
|
+
|
|
27
|
+
waken = Waken(api_key=os.environ["WAKEN_KEY"])
|
|
28
|
+
|
|
29
|
+
agent = waken.agents.create(
|
|
30
|
+
name="bookkeeper",
|
|
31
|
+
model={"provider": "anthropic", "api_key": os.environ["ANTHROPIC_API_KEY"]},
|
|
32
|
+
spend_cap_usd=10,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
reply = waken.agents.message(agent["id"], "Sort my March receipts by vendor.")
|
|
36
|
+
print(reply["text"])
|
|
37
|
+
|
|
38
|
+
cp = waken.checkpoints.take(agent["id"]) # a point you can rewind to
|
|
39
|
+
waken.checkpoints.restore(agent["id"], cp["id"]) # rewind: files, memory, conversation
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Everything the API does is here: `agents` (create, list, get, delete, message, sleep, wake, restart, usage, resize, set_model, set_harness, logs, exec), `checkpoints` (take, list, restore), `files` (list, read, read_text, write, mkdir, move, delete, upload_tar), `env` (list, set), `triggers` (schedules and the agent's incoming webhook), `webhooks` (events on all your agents), `harnesses()`. Any other route: `waken.request(method, path, body)`.
|
|
43
|
+
|
|
44
|
+
Errors raise `WakenError` with `.status` and the API message.
|
|
45
|
+
|
|
46
|
+
Keys are created in the dashboard, Developers. An agent runs on your own model key, passed at creation and never stored by Waken.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
waken
|