aindy-sdk 1.0.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.
- aindy_sdk-1.0.0/PKG-INFO +371 -0
- aindy_sdk-1.0.0/README.md +356 -0
- aindy_sdk-1.0.0/aindy_sdk/__init__.py +66 -0
- aindy_sdk-1.0.0/aindy_sdk/_version.py +1 -0
- aindy_sdk-1.0.0/aindy_sdk/client.py +163 -0
- aindy_sdk-1.0.0/aindy_sdk/events.py +63 -0
- aindy_sdk-1.0.0/aindy_sdk/exceptions.py +108 -0
- aindy_sdk-1.0.0/aindy_sdk/execution.py +59 -0
- aindy_sdk-1.0.0/aindy_sdk/flow.py +61 -0
- aindy_sdk-1.0.0/aindy_sdk/memory.py +182 -0
- aindy_sdk-1.0.0/aindy_sdk/nodus.py +130 -0
- aindy_sdk-1.0.0/aindy_sdk/sandbox.py +101 -0
- aindy_sdk-1.0.0/aindy_sdk/syscalls.py +87 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/__init__.py +5 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/classifier.py +292 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/config.py +116 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/session_tracker.py +338 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/signal_emitter.py +201 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/watcher.py +146 -0
- aindy_sdk-1.0.0/aindy_sdk/watcher/window_detector.py +249 -0
- aindy_sdk-1.0.0/aindy_sdk.egg-info/PKG-INFO +371 -0
- aindy_sdk-1.0.0/aindy_sdk.egg-info/SOURCES.txt +27 -0
- aindy_sdk-1.0.0/aindy_sdk.egg-info/dependency_links.txt +1 -0
- aindy_sdk-1.0.0/aindy_sdk.egg-info/requires.txt +4 -0
- aindy_sdk-1.0.0/aindy_sdk.egg-info/top_level.txt +1 -0
- aindy_sdk-1.0.0/pyproject.toml +35 -0
- aindy_sdk-1.0.0/setup.cfg +4 -0
- aindy_sdk-1.0.0/tests/test_sdk.py +595 -0
- aindy_sdk-1.0.0/tests/test_watcher.py +92 -0
aindy_sdk-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aindy-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Client SDK for the A.I.N.D.Y. runtime API
|
|
5
|
+
Author: Shawn Knight
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: pytest==9.0.2; extra == "test"
|
|
14
|
+
Requires-Dist: pytest-cov==7.0.0; extra == "test"
|
|
15
|
+
|
|
16
|
+
# aindy-sdk
|
|
17
|
+
|
|
18
|
+
Universal interface SDK for the A.I.N.D.Y. runtime API.
|
|
19
|
+
|
|
20
|
+
`aindy-sdk` is the local+cloud bridge: the same client code works against a
|
|
21
|
+
locally-installed runtime (where the operator owns the infrastructure) and a
|
|
22
|
+
cloud-hosted runtime (where the provider owns the infrastructure). Switching
|
|
23
|
+
deployment context requires only a `base_url` change — no application code
|
|
24
|
+
changes.
|
|
25
|
+
|
|
26
|
+
Part of the Masterplan Infinite Weave ecosystem by Shawn Knight.
|
|
27
|
+
Runtime infrastructure: https://github.com/Masterplanner25/aindy-runtime
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install aindy-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.11+
|
|
38
|
+
- No external dependencies (stdlib only) for the API client
|
|
39
|
+
- Watcher subpackage optional dependencies: `psutil` (fallback window detection),
|
|
40
|
+
`pyobjc-framework-AppKit` + `pyobjc-framework-Quartz` (macOS window titles),
|
|
41
|
+
`xdotool` system package (Linux window detection)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Getting started
|
|
46
|
+
|
|
47
|
+
### 1. Start the runtime
|
|
48
|
+
|
|
49
|
+
Install and start `aindy-runtime` ([full instructions](https://github.com/Masterplanner25/aindy-runtime#quickstart)):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/Masterplanner25/aindy-runtime.git
|
|
53
|
+
cd aindy-runtime
|
|
54
|
+
cp AINDY/.env.example AINDY/.env # set SECRET_KEY and OPENAI_API_KEY at minimum
|
|
55
|
+
docker compose up -d
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Wait until the server is ready:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
curl http://localhost:8000/ready # → {"status": "ok", ...}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Register and log in
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Register
|
|
68
|
+
curl -s -X POST http://localhost:8000/auth/register \
|
|
69
|
+
-H "Content-Type: application/json" \
|
|
70
|
+
-d '{"email": "you@example.com", "password": "yourpassword", "display_name": "You"}' \
|
|
71
|
+
| python -m json.tool
|
|
72
|
+
|
|
73
|
+
# Log in — copy the access_token from the response
|
|
74
|
+
curl -s -X POST http://localhost:8000/auth/login \
|
|
75
|
+
-H "Content-Type: application/json" \
|
|
76
|
+
-d '{"email": "you@example.com", "password": "yourpassword"}' \
|
|
77
|
+
| python -m json.tool
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
To promote your account to admin (required to create API keys via the CLI):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Using the CLI (no server restart needed)
|
|
84
|
+
aindy-runtime auth promote-admin you@example.com
|
|
85
|
+
|
|
86
|
+
# Or via env var (requires server restart)
|
|
87
|
+
# Add AINDY_BOOTSTRAP_ADMIN_EMAIL=you@example.com to AINDY/.env, then docker compose restart api
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3. Create a Platform API key
|
|
91
|
+
|
|
92
|
+
Platform API keys start with `aindy_` and are shown only once — save the `key` field.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
JWT="<your-access_token>"
|
|
96
|
+
|
|
97
|
+
curl -s -X POST http://localhost:8000/platform/keys \
|
|
98
|
+
-H "Authorization: Bearer $JWT" \
|
|
99
|
+
-H "Content-Type: application/json" \
|
|
100
|
+
-d '{
|
|
101
|
+
"name": "my-app",
|
|
102
|
+
"scopes": ["memory.read", "memory.write", "flow.run", "event.emit"]
|
|
103
|
+
}' \
|
|
104
|
+
| python -m json.tool
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Available scopes:**
|
|
108
|
+
|
|
109
|
+
| Scope | Grants access to |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `memory.read` | Read and search memory nodes |
|
|
112
|
+
| `memory.write` | Write and delete memory nodes |
|
|
113
|
+
| `flow.run` | Trigger flow runs |
|
|
114
|
+
| `event.emit` | Emit events to the event bus |
|
|
115
|
+
| `syscall.*` | Full syscall registry access |
|
|
116
|
+
| `execution.read` | Read execution unit status |
|
|
117
|
+
|
|
118
|
+
### 4. First call
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from aindy_sdk import AINDYClient
|
|
122
|
+
|
|
123
|
+
client = AINDYClient(
|
|
124
|
+
base_url="http://localhost:8000",
|
|
125
|
+
api_key="aindy_your_platform_key",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
result = client.memory.read("/memory/you/notes/**", limit=5)
|
|
129
|
+
print(result["data"]["nodes"])
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Run the full example:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
AINDY_API_KEY=aindy_... python examples/quickstart.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Usage
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from aindy_sdk import AINDYClient
|
|
144
|
+
|
|
145
|
+
# Local runtime (operator owns infrastructure)
|
|
146
|
+
client = AINDYClient(
|
|
147
|
+
base_url="http://localhost:8000",
|
|
148
|
+
api_key="aindy_your_platform_key",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Cloud-hosted — same code, different base_url
|
|
152
|
+
# client = AINDYClient(base_url="https://runtime.aindy.ai", api_key="...")
|
|
153
|
+
|
|
154
|
+
# Read memory
|
|
155
|
+
result = client.memory.read("/memory/shawn/entities/**", limit=5)
|
|
156
|
+
nodes = result["data"]["nodes"]
|
|
157
|
+
|
|
158
|
+
# Run a flow
|
|
159
|
+
analysis = client.flow.run("analyze_entities", {"data": nodes})
|
|
160
|
+
|
|
161
|
+
# Write memory
|
|
162
|
+
client.memory.write(
|
|
163
|
+
"/memory/shawn/insights/outcome",
|
|
164
|
+
analysis["data"].get("summary", ""),
|
|
165
|
+
tags=["sdk-demo"],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Emit an event
|
|
169
|
+
client.events.emit("analysis.completed", {"node_count": len(nodes)})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## API reference
|
|
175
|
+
|
|
176
|
+
All capabilities are accessed through namespaces on `AINDYClient`:
|
|
177
|
+
|
|
178
|
+
| Namespace | Description |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `client.memory` | Read, write, and search memory nodes by MAS path or vector similarity |
|
|
181
|
+
| `client.flow` | Trigger flow runs and poll their status |
|
|
182
|
+
| `client.events` | Emit events to the runtime event bus |
|
|
183
|
+
| `client.nodus` | Compile and execute Nodus scripts |
|
|
184
|
+
| `client.syscalls` | List and inspect the syscall registry |
|
|
185
|
+
| `client.execution` | Introspect in-flight or completed execution units |
|
|
186
|
+
| `client.sandbox` | Query sandbox assurance posture |
|
|
187
|
+
|
|
188
|
+
Every method returns a **syscall envelope**:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
{
|
|
192
|
+
"status": "success", # "success" or "error"
|
|
193
|
+
"data": { ... }, # payload — varies by call
|
|
194
|
+
"trace_id": "abc123...", # for log correlation
|
|
195
|
+
"duration_ms": 12, # server-side execution time in ms
|
|
196
|
+
"error": None, # error description when status == "error"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Memory (`client.memory`)
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
# Read nodes at a MAS path (glob patterns supported)
|
|
204
|
+
result = client.memory.read("/memory/shawn/entities/**", limit=10)
|
|
205
|
+
nodes = result["data"]["nodes"]
|
|
206
|
+
|
|
207
|
+
# Write a node
|
|
208
|
+
result = client.memory.write(
|
|
209
|
+
"/memory/shawn/insights/daily-summary",
|
|
210
|
+
"Worked on SDK documentation today.",
|
|
211
|
+
tags=["insight", "daily"],
|
|
212
|
+
node_type="insight",
|
|
213
|
+
)
|
|
214
|
+
node_id = result["data"]["node"]["id"]
|
|
215
|
+
|
|
216
|
+
# Semantic similarity search
|
|
217
|
+
result = client.memory.search("recent project decisions", limit=5)
|
|
218
|
+
|
|
219
|
+
# Delete a node
|
|
220
|
+
client.memory.delete(node_id)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Flow (`client.flow`)
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
# Trigger a flow run and get the result
|
|
227
|
+
result = client.flow.run("analyze_entities", {"data": nodes})
|
|
228
|
+
print(result["status"]) # "success" | "waiting" | "failed"
|
|
229
|
+
|
|
230
|
+
# For long-running flows: trigger and poll
|
|
231
|
+
result = client.flow.trigger("long_analysis", {"input": "..."})
|
|
232
|
+
run_id = result["data"]["run_id"]
|
|
233
|
+
|
|
234
|
+
import time
|
|
235
|
+
while True:
|
|
236
|
+
status = client.execution.get(run_id)
|
|
237
|
+
if status["data"]["status"] not in ("running", "waiting"):
|
|
238
|
+
break
|
|
239
|
+
time.sleep(2)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Events (`client.events`)
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
# Emit an event (can trigger waiting flows)
|
|
246
|
+
result = client.events.emit(
|
|
247
|
+
"user.action.completed",
|
|
248
|
+
{"user_id": "u-123", "action": "document_saved"},
|
|
249
|
+
)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Nodus (`client.nodus`)
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# Execute a Nodus script
|
|
256
|
+
result = client.nodus.run_script(
|
|
257
|
+
script="""
|
|
258
|
+
let greeting = "Hello from Nodus"
|
|
259
|
+
set_state("message", greeting)
|
|
260
|
+
emit("script.hello", {text: greeting})
|
|
261
|
+
""",
|
|
262
|
+
input={},
|
|
263
|
+
)
|
|
264
|
+
print(result["nodus_status"]) # "complete" | "wait" | "error"
|
|
265
|
+
print(result["output_state"]) # dict of set_state() calls
|
|
266
|
+
print(result["events_emitted"]) # count of emit() calls
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Execution (`client.execution`)
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
run = client.execution.get("run-abc123")
|
|
273
|
+
status = run["data"]["status"] # "running" | "success" | "failed" | "waiting"
|
|
274
|
+
syscall_count = run["data"]["syscall_count"]
|
|
275
|
+
wall_time_ms = run["data"]["wall_time_ms"] # accumulated wall-clock time (includes I/O wait)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Syscalls (`client.syscalls`)
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
registry = client.syscalls.list(version="v1")
|
|
282
|
+
print(registry["total_count"])
|
|
283
|
+
for action, spec in registry["syscalls"]["v1"].items():
|
|
284
|
+
deprecated = " [DEPRECATED]" if spec.get("deprecated") else ""
|
|
285
|
+
print(f"sys.v1.{action}{deprecated}")
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Error handling
|
|
291
|
+
|
|
292
|
+
The SDK maps HTTP status codes to typed exceptions so you can handle failure
|
|
293
|
+
cases explicitly rather than inspecting status codes:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from aindy_sdk import AINDYClient
|
|
297
|
+
from aindy_sdk.exceptions import (
|
|
298
|
+
AuthenticationError, # 401 — key missing, invalid, or expired
|
|
299
|
+
PermissionDeniedError, # 403 — key scope doesn't include this capability
|
|
300
|
+
NotFoundError, # 404 — resource doesn't exist
|
|
301
|
+
ValidationError, # 422 — bad request shape or schema violation
|
|
302
|
+
ResourceLimitError, # 429 — syscall count or wall_time_ms exceeded
|
|
303
|
+
ServerError, # 5xx — unexpected runtime error
|
|
304
|
+
NetworkError, # connection refused, timeout, or DNS failure
|
|
305
|
+
AINDYError, # base class — catches all of the above
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
client = AINDYClient(base_url="http://localhost:8000", api_key="aindy_...")
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
result = client.memory.read("/memory/shawn/entities/**", limit=5)
|
|
312
|
+
except AuthenticationError:
|
|
313
|
+
print("Invalid API key — check AINDY_API_KEY")
|
|
314
|
+
except PermissionDeniedError as exc:
|
|
315
|
+
print(f"Key scope missing: {exc.message}")
|
|
316
|
+
# exc.response contains the full server error payload
|
|
317
|
+
except ValidationError as exc:
|
|
318
|
+
print(f"Bad request: {exc.message}")
|
|
319
|
+
except NetworkError as exc:
|
|
320
|
+
print(f"Server unreachable: {exc.cause}")
|
|
321
|
+
except AINDYError as exc:
|
|
322
|
+
print(f"Runtime error [{exc.status_code}]: {exc.message}")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
All typed errors carry:
|
|
326
|
+
- `.message` — human-readable description
|
|
327
|
+
- `.status_code` — HTTP status code (`None` for `NetworkError`)
|
|
328
|
+
- `.response` — raw server JSON dict
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Watcher
|
|
333
|
+
|
|
334
|
+
The SDK ships the A.I.N.D.Y. Watcher client process. The watcher runs on your
|
|
335
|
+
machine, observes the active OS window, classifies your activity, and emits
|
|
336
|
+
structured signals to the runtime for flow triggering and session tracking.
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# Start the watcher (requires AINDY_API_KEY and AINDY_WATCHER_API_URL)
|
|
340
|
+
python -m aindy_sdk.watcher.watcher
|
|
341
|
+
|
|
342
|
+
# Dry run — logs signals, no HTTP requests
|
|
343
|
+
python -m aindy_sdk.watcher.watcher --dry-run
|
|
344
|
+
|
|
345
|
+
# Custom poll interval and verbose logging
|
|
346
|
+
python -m aindy_sdk.watcher.watcher --poll-interval 10 --log-level DEBUG
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Environment variables:
|
|
350
|
+
|
|
351
|
+
| Variable | Default | Description |
|
|
352
|
+
|---|---|---|
|
|
353
|
+
| `AINDY_WATCHER_API_URL` | `http://localhost:8000` | Runtime base URL |
|
|
354
|
+
| `AINDY_API_KEY` | _(required)_ | Service API key (`X-API-Key` header) |
|
|
355
|
+
| `AINDY_WATCHER_DRY_RUN` | `false` | Log signals only, no HTTP POST |
|
|
356
|
+
| `AINDY_WATCHER_POLL_INTERVAL` | `5` | Seconds between window samples |
|
|
357
|
+
| `AINDY_WATCHER_FLUSH_INTERVAL` | `10` | Seconds between signal batch flushes |
|
|
358
|
+
| `AINDY_WATCHER_BATCH_SIZE` | `20` | Signals per POST batch |
|
|
359
|
+
| `AINDY_WATCHER_CONFIRMATION_DELAY` | `30` | Seconds of focus before `session_started` fires |
|
|
360
|
+
| `AINDY_WATCHER_DISTRACTION_TIMEOUT` | `60` | Seconds of off-task time before distraction state |
|
|
361
|
+
| `AINDY_WATCHER_RECOVERY_DELAY` | `30` | Seconds of focus to recover from distraction |
|
|
362
|
+
| `AINDY_WATCHER_HEARTBEAT_INTERVAL` | `300` | Seconds between heartbeat signals |
|
|
363
|
+
| `AINDY_WATCHER_LOG_LEVEL` | `INFO` | `DEBUG` \| `INFO` \| `WARNING` \| `ERROR` |
|
|
364
|
+
|
|
365
|
+
See `examples/watcher_demo.py` for a dry-run demonstration.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Status
|
|
370
|
+
|
|
371
|
+
Beta. Part of the aindy-runtime 1.0.0 ecosystem.
|