opennous 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.
- opennous-1.0.0/PKG-INFO +11 -0
- opennous-1.0.0/README.md +53 -0
- opennous-1.0.0/opennous/__init__.py +3 -0
- opennous-1.0.0/opennous/client.py +382 -0
- opennous-1.0.0/opennous.egg-info/PKG-INFO +11 -0
- opennous-1.0.0/opennous.egg-info/SOURCES.txt +9 -0
- opennous-1.0.0/opennous.egg-info/dependency_links.txt +1 -0
- opennous-1.0.0/opennous.egg-info/requires.txt +1 -0
- opennous-1.0.0/opennous.egg-info/top_level.txt +1 -0
- opennous-1.0.0/pyproject.toml +23 -0
- opennous-1.0.0/setup.cfg +4 -0
opennous-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opennous
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Nous — GTM data infrastructure for agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://opennous.cloud
|
|
7
|
+
Project-URL: Repository, https://github.com/bennetglinder1/nous
|
|
8
|
+
Project-URL: Documentation, https://docs.opennous.cloud
|
|
9
|
+
Keywords: nous,opennous,ai,agents,crm,memory,sdk,mcp
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
opennous-1.0.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# opennous · Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [Nous](https://opennous.cloud) API — GTM data infrastructure for agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install opennous
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from opennous import NousClient
|
|
15
|
+
|
|
16
|
+
client = NousClient(api_key="your-api-key")
|
|
17
|
+
|
|
18
|
+
# Get full contact profile before acting
|
|
19
|
+
contact = client.get_contact("sarah@acme.com")
|
|
20
|
+
print(contact["summary"])
|
|
21
|
+
|
|
22
|
+
# Log an interaction
|
|
23
|
+
client.track(email="sarah@acme.com", type="call_held", description="30 min discovery call")
|
|
24
|
+
|
|
25
|
+
# Store a fact
|
|
26
|
+
client.remember(email="sarah@acme.com", text="Concerned about Salesforce migration and Q3 budget.")
|
|
27
|
+
|
|
28
|
+
# Store workspace-level facts
|
|
29
|
+
client.remember(text="ICP: technical founders of AI sales tools, 2-20 people.", category="ICP")
|
|
30
|
+
|
|
31
|
+
# Semantic search
|
|
32
|
+
results = client.search("budget concerns")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Auth
|
|
36
|
+
|
|
37
|
+
Set your API key via env var or pass directly:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export NOUS_API_KEY=your-api-key
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
client = NousClient() # picks up NOUS_API_KEY automatically
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Docs
|
|
48
|
+
|
|
49
|
+
Full API reference: [docs.opennous.cloud](https://docs.opennous.cloud)
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""Nous Python SDK — contact memory for AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Literal, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
DEFAULT_BASE_URL = "https://api.opennous.cloud"
|
|
11
|
+
|
|
12
|
+
ActivityType = Literal[
|
|
13
|
+
"email_sent", "email_reply",
|
|
14
|
+
"call_held", "meeting_held",
|
|
15
|
+
"linkedin_message", "linkedin_connected",
|
|
16
|
+
"follow_up_sent", "proposal_sent",
|
|
17
|
+
"website_visit", "content_download", "trial_started",
|
|
18
|
+
"manual_note",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
MemoryCategory = Literal[
|
|
22
|
+
"ICP", "Product", "Pricing", "Market",
|
|
23
|
+
"Competitors", "Team", "Patterns", "General",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NousError(Exception):
|
|
28
|
+
def __init__(self, message: str, status: int, code: str | None = None) -> None:
|
|
29
|
+
super().__init__(message)
|
|
30
|
+
self.status = status
|
|
31
|
+
self.code = code
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NousClient:
|
|
35
|
+
"""
|
|
36
|
+
Nous contact memory client.
|
|
37
|
+
|
|
38
|
+
Usage::
|
|
39
|
+
|
|
40
|
+
from opennous import NousClient
|
|
41
|
+
|
|
42
|
+
client = NousClient(api_key="YOUR_API_KEY")
|
|
43
|
+
|
|
44
|
+
# Before acting on a contact
|
|
45
|
+
contact = client.get_contact("sarah@acme.com")
|
|
46
|
+
print(contact["summary"])
|
|
47
|
+
|
|
48
|
+
# After an interaction
|
|
49
|
+
client.track(email="sarah@acme.com", type="call_held", description="30 min discovery call")
|
|
50
|
+
client.remember(email="sarah@acme.com", text="Concerned about Salesforce migration.")
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
api_key: str | None = None,
|
|
56
|
+
base_url: str | None = None,
|
|
57
|
+
timeout: float = 30.0,
|
|
58
|
+
) -> None:
|
|
59
|
+
self._api_key = api_key or os.environ.get("NOUS_API_KEY")
|
|
60
|
+
if not self._api_key:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"api_key is required. Pass it explicitly or set the NOUS_API_KEY environment variable."
|
|
63
|
+
)
|
|
64
|
+
self._base_url = (base_url or os.environ.get("NOUS_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
65
|
+
self._client = httpx.Client(
|
|
66
|
+
base_url=self._base_url,
|
|
67
|
+
headers={"Authorization": f"Bearer {self._api_key}", "X-Nous-Client": "sdk-python"},
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
72
|
+
res = self._client.get(path, params=params)
|
|
73
|
+
return self._handle(res)
|
|
74
|
+
|
|
75
|
+
def _post(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
76
|
+
res = self._client.post(path, json=body)
|
|
77
|
+
return self._handle(res)
|
|
78
|
+
|
|
79
|
+
def _patch(self, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
80
|
+
res = self._client.patch(path, json=body)
|
|
81
|
+
return self._handle(res)
|
|
82
|
+
|
|
83
|
+
def _delete(self, path: str) -> dict[str, Any]:
|
|
84
|
+
res = self._client.delete(path)
|
|
85
|
+
return self._handle(res)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _handle(res: httpx.Response) -> dict[str, Any]:
|
|
89
|
+
if not res.is_success:
|
|
90
|
+
try:
|
|
91
|
+
err = res.json()
|
|
92
|
+
msg = err.get("message") or err.get("error") or res.reason_phrase
|
|
93
|
+
code = err.get("error")
|
|
94
|
+
except Exception:
|
|
95
|
+
msg = res.reason_phrase
|
|
96
|
+
code = None
|
|
97
|
+
raise NousError(msg, res.status_code, code)
|
|
98
|
+
if res.status_code == 204:
|
|
99
|
+
return {}
|
|
100
|
+
return res.json()
|
|
101
|
+
|
|
102
|
+
# ── Activity ──────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def track(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
email: str | None = None,
|
|
108
|
+
contact_id: str | None = None,
|
|
109
|
+
type: ActivityType,
|
|
110
|
+
description: str | None = None,
|
|
111
|
+
occurred_at: str | None = None,
|
|
112
|
+
source: str = "sdk",
|
|
113
|
+
) -> dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Log that something happened with a contact.
|
|
116
|
+
Auto-creates the contact if they don't exist yet.
|
|
117
|
+
|
|
118
|
+
:param email: Contact email address (required if contact_id not given)
|
|
119
|
+
:param contact_id: Contact UUID (required if email not given)
|
|
120
|
+
:param type: Activity type — e.g. "call_held", "email_sent"
|
|
121
|
+
:param description: Brief summary of what happened
|
|
122
|
+
:param occurred_at: ISO timestamp (defaults to now)
|
|
123
|
+
:returns: { contact_id, activity_id, type, occurred_at, created_contact }
|
|
124
|
+
"""
|
|
125
|
+
if not email and not contact_id:
|
|
126
|
+
raise ValueError("Provide either email or contact_id")
|
|
127
|
+
body: dict[str, Any] = {"type": type, "source": source}
|
|
128
|
+
if email: body["email"] = email
|
|
129
|
+
if contact_id: body["contact_id"] = contact_id
|
|
130
|
+
if description: body["description"] = description
|
|
131
|
+
if occurred_at: body["occurred_at"] = occurred_at
|
|
132
|
+
return self._post("/v1/track", body)
|
|
133
|
+
|
|
134
|
+
# ── Memory ────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
def remember(
|
|
137
|
+
self,
|
|
138
|
+
*,
|
|
139
|
+
email: str | None = None,
|
|
140
|
+
contact_id: str | None = None,
|
|
141
|
+
company_id: str | None = None,
|
|
142
|
+
text: str,
|
|
143
|
+
category: MemoryCategory = "General",
|
|
144
|
+
source: str = "sdk",
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
"""
|
|
147
|
+
Store what was learned about a contact, company, or workspace.
|
|
148
|
+
Pass a single sentence or a full transcript — AI extracts durable facts either way.
|
|
149
|
+
Omit email, contact_id, and company_id to store workspace-level facts (ICP, product, market).
|
|
150
|
+
|
|
151
|
+
:param text: The text to extract facts from
|
|
152
|
+
:param category: Memory category (ICP, Product, Pricing, etc.)
|
|
153
|
+
:returns: { stored: int, facts: list[{ id, content, written_at }] }
|
|
154
|
+
"""
|
|
155
|
+
body: dict[str, Any] = {"text": text, "category": category, "source": source}
|
|
156
|
+
if email: body["email"] = email
|
|
157
|
+
if contact_id: body["contact_id"] = contact_id
|
|
158
|
+
if company_id: body["company_id"] = company_id
|
|
159
|
+
return self._post("/v1/remember", body)
|
|
160
|
+
|
|
161
|
+
def get_memories(
|
|
162
|
+
self,
|
|
163
|
+
*,
|
|
164
|
+
category: str | None = None,
|
|
165
|
+
limit: int = 50,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Load all workspace-level facts — ICP, product, pricing, market, competitive intel.
|
|
169
|
+
Call before drafting outreach or any task requiring workspace context.
|
|
170
|
+
|
|
171
|
+
:param category: Optional filter — ICP, Product, Pricing, Market, Competitors, Team, Patterns, General
|
|
172
|
+
:param limit: Max facts to return (default 50, max 200)
|
|
173
|
+
:returns: { memories: list[{ id, category, content, created_at }], total: int }
|
|
174
|
+
"""
|
|
175
|
+
params: dict[str, Any] = {"limit": limit}
|
|
176
|
+
if category: params["category"] = category
|
|
177
|
+
return self._get("/v1/memories", params=params)
|
|
178
|
+
|
|
179
|
+
def search(
|
|
180
|
+
self,
|
|
181
|
+
q: str,
|
|
182
|
+
*,
|
|
183
|
+
contact_id: Optional[str] = None,
|
|
184
|
+
company_id: Optional[str] = None,
|
|
185
|
+
limit: int = 10,
|
|
186
|
+
threshold: Optional[float] = None,
|
|
187
|
+
) -> dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Semantic search across workspace memories.
|
|
190
|
+
|
|
191
|
+
:param q: Search query
|
|
192
|
+
:param contact_id: Scope search to one contact (uses lenient threshold 0.45)
|
|
193
|
+
:param company_id: Scope search to one company
|
|
194
|
+
:param limit: Max results (default 10)
|
|
195
|
+
:param threshold: Override similarity threshold (0–1)
|
|
196
|
+
:returns: { results: list, count: int }
|
|
197
|
+
"""
|
|
198
|
+
body: dict[str, Any] = {"q": q, "limit": limit}
|
|
199
|
+
if contact_id: body["contact_id"] = contact_id
|
|
200
|
+
if company_id: body["company_id"] = company_id
|
|
201
|
+
if threshold is not None: body["threshold"] = threshold
|
|
202
|
+
return self._post("/v1/search", body)
|
|
203
|
+
|
|
204
|
+
def delete_memory(self, memory_id: str) -> dict[str, Any]:
|
|
205
|
+
"""
|
|
206
|
+
Soft-delete a workspace memory by UUID.
|
|
207
|
+
Get the ID from get_memories(). Marks the fact inactive — won't appear in future reads.
|
|
208
|
+
|
|
209
|
+
:param memory_id: Memory UUID
|
|
210
|
+
:returns: { deleted: True, id, content }
|
|
211
|
+
"""
|
|
212
|
+
from urllib.parse import quote
|
|
213
|
+
return self._delete(f"/v1/memory/{quote(memory_id, safe='')}")
|
|
214
|
+
|
|
215
|
+
# ── Contacts ──────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
def get_contact(self, identifier: str) -> dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Full contact profile — structured JSON.
|
|
220
|
+
Returns identity, pipeline stage, AI summary, scores, channels, last 25 activities
|
|
221
|
+
(with message body where available), facts, and company details.
|
|
222
|
+
|
|
223
|
+
:param identifier: Email address or contact UUID
|
|
224
|
+
:returns: Full contact profile dict
|
|
225
|
+
"""
|
|
226
|
+
from urllib.parse import quote
|
|
227
|
+
return self._get(f"/v1/contacts/{quote(identifier, safe='')}")
|
|
228
|
+
|
|
229
|
+
def get_contact_activity(
|
|
230
|
+
self,
|
|
231
|
+
identifier: str,
|
|
232
|
+
*,
|
|
233
|
+
limit: int = 20,
|
|
234
|
+
offset: int = 0,
|
|
235
|
+
type: Optional[str] = None,
|
|
236
|
+
before: Optional[str] = None,
|
|
237
|
+
after: Optional[str] = None,
|
|
238
|
+
) -> dict[str, Any]:
|
|
239
|
+
"""
|
|
240
|
+
Paginated activity history for a contact.
|
|
241
|
+
Use when total_activities is high or you need to filter by type / date range.
|
|
242
|
+
Each activity includes `body` (message text) where available.
|
|
243
|
+
|
|
244
|
+
:param identifier: Email address or contact UUID
|
|
245
|
+
:param limit: Number of activities to return (default 20, max 100)
|
|
246
|
+
:param offset: Pagination offset
|
|
247
|
+
:param type: Filter by type e.g. "linkedin_message", "email_received"
|
|
248
|
+
:param before: ISO date — return activities before this date
|
|
249
|
+
:param after: ISO date — return activities after this date
|
|
250
|
+
:returns: { activities: list, total: int, limit: int, offset: int }
|
|
251
|
+
"""
|
|
252
|
+
from urllib.parse import quote
|
|
253
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
254
|
+
if type: params["type"] = type
|
|
255
|
+
if before: params["before"] = before
|
|
256
|
+
if after: params["after"] = after
|
|
257
|
+
return self._get(f"/v1/contacts/{quote(identifier, safe='')}/activity", params=params)
|
|
258
|
+
|
|
259
|
+
def list_contacts(
|
|
260
|
+
self,
|
|
261
|
+
*,
|
|
262
|
+
stage: Optional[str] = None,
|
|
263
|
+
search: Optional[str] = None,
|
|
264
|
+
linkedin_url: Optional[str] = None,
|
|
265
|
+
limit: int = 20,
|
|
266
|
+
offset: int = 0,
|
|
267
|
+
) -> dict[str, Any]:
|
|
268
|
+
"""
|
|
269
|
+
List contacts, optionally filtered by pipeline stage or LinkedIn URL.
|
|
270
|
+
|
|
271
|
+
:param stage: Pipeline stage filter — identified | aware | interested | evaluating | client
|
|
272
|
+
:param search: Search query (name, email, or company)
|
|
273
|
+
:param linkedin_url: Exact LinkedIn profile URL filter (normalized before matching)
|
|
274
|
+
:param limit: Max contacts to return (default 20, max 100)
|
|
275
|
+
:param offset: Pagination offset
|
|
276
|
+
:returns: { contacts: list, total: int }
|
|
277
|
+
"""
|
|
278
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
279
|
+
if stage: params["stage"] = stage
|
|
280
|
+
if search: params["search"] = search
|
|
281
|
+
if linkedin_url: params["linkedin_url"] = linkedin_url
|
|
282
|
+
return self._get("/v1/contacts", params=params)
|
|
283
|
+
|
|
284
|
+
def create_contact(
|
|
285
|
+
self,
|
|
286
|
+
*,
|
|
287
|
+
email: Optional[str] = None,
|
|
288
|
+
first_name: Optional[str] = None,
|
|
289
|
+
last_name: Optional[str] = None,
|
|
290
|
+
company: Optional[str] = None,
|
|
291
|
+
job_title: Optional[str] = None,
|
|
292
|
+
phone: Optional[str] = None,
|
|
293
|
+
linkedin_url: Optional[str] = None,
|
|
294
|
+
notes: Optional[str] = None,
|
|
295
|
+
) -> dict[str, Any]:
|
|
296
|
+
"""
|
|
297
|
+
Create a new contact with full profile fields.
|
|
298
|
+
email is required unless linkedin_url is provided.
|
|
299
|
+
Returns 409 if a contact with that email or LinkedIn URL already exists.
|
|
300
|
+
|
|
301
|
+
:param email: Email address (required if linkedin_url not given, must be unique)
|
|
302
|
+
:param linkedin_url: LinkedIn profile URL (required if email not given, must be unique)
|
|
303
|
+
:returns: { id, email, name, company, job_title, pipeline_stage, created_at }
|
|
304
|
+
"""
|
|
305
|
+
if not email and not linkedin_url:
|
|
306
|
+
raise ValueError("Provide either email or linkedin_url")
|
|
307
|
+
body: dict[str, Any] = {}
|
|
308
|
+
if email: body["email"] = email
|
|
309
|
+
if first_name: body["first_name"] = first_name
|
|
310
|
+
if last_name: body["last_name"] = last_name
|
|
311
|
+
if company: body["company"] = company
|
|
312
|
+
if job_title: body["job_title"] = job_title
|
|
313
|
+
if phone: body["phone"] = phone
|
|
314
|
+
if linkedin_url: body["linkedin_url"] = linkedin_url
|
|
315
|
+
if notes: body["notes"] = notes
|
|
316
|
+
return self._post("/v1/contacts", body)
|
|
317
|
+
|
|
318
|
+
def update_contact(
|
|
319
|
+
self,
|
|
320
|
+
identifier: str,
|
|
321
|
+
*,
|
|
322
|
+
first_name: Optional[str] = None,
|
|
323
|
+
last_name: Optional[str] = None,
|
|
324
|
+
company: Optional[str] = None,
|
|
325
|
+
job_title: Optional[str] = None,
|
|
326
|
+
phone: Optional[str] = None,
|
|
327
|
+
linkedin_url: Optional[str] = None,
|
|
328
|
+
notes: Optional[str] = None,
|
|
329
|
+
) -> dict[str, Any]:
|
|
330
|
+
"""
|
|
331
|
+
Update one or more profile fields on an existing contact.
|
|
332
|
+
Only provided fields are changed.
|
|
333
|
+
|
|
334
|
+
:param identifier: Email address or contact UUID
|
|
335
|
+
:returns: { id, email, name, company, job_title, pipeline_stage }
|
|
336
|
+
"""
|
|
337
|
+
from urllib.parse import quote
|
|
338
|
+
body: dict[str, Any] = {}
|
|
339
|
+
if first_name is not None: body["first_name"] = first_name
|
|
340
|
+
if last_name is not None: body["last_name"] = last_name
|
|
341
|
+
if company is not None: body["company"] = company
|
|
342
|
+
if job_title is not None: body["job_title"] = job_title
|
|
343
|
+
if phone is not None: body["phone"] = phone
|
|
344
|
+
if linkedin_url is not None: body["linkedin_url"] = linkedin_url
|
|
345
|
+
if notes is not None: body["notes"] = notes
|
|
346
|
+
return self._patch(f"/v1/contacts/{quote(identifier, safe='')}", body)
|
|
347
|
+
|
|
348
|
+
def delete_contact(self, identifier: str) -> dict[str, Any]:
|
|
349
|
+
"""
|
|
350
|
+
Permanently delete a contact and all their data — activities and memories.
|
|
351
|
+
Cannot be undone. Pass email address or contact UUID.
|
|
352
|
+
|
|
353
|
+
:param identifier: Email address or contact UUID
|
|
354
|
+
:returns: { deleted: True, contact_id, email }
|
|
355
|
+
"""
|
|
356
|
+
from urllib.parse import quote
|
|
357
|
+
return self._delete(f"/v1/contacts/{quote(identifier, safe='')}")
|
|
358
|
+
|
|
359
|
+
# ── Company ───────────────────────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
def get_company(self, company_id: str) -> dict[str, Any]:
|
|
362
|
+
"""
|
|
363
|
+
Full token-budgeted company profile.
|
|
364
|
+
Returns org details + all contacts + company facts.
|
|
365
|
+
|
|
366
|
+
:param company_id: Company UUID
|
|
367
|
+
:returns: Full company profile dict
|
|
368
|
+
"""
|
|
369
|
+
from urllib.parse import quote
|
|
370
|
+
return self._get(f"/v1/company/{quote(company_id, safe='')}")
|
|
371
|
+
|
|
372
|
+
# ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
373
|
+
|
|
374
|
+
def close(self) -> None:
|
|
375
|
+
"""Close the underlying HTTP client."""
|
|
376
|
+
self._client.close()
|
|
377
|
+
|
|
378
|
+
def __enter__(self) -> "NousClient":
|
|
379
|
+
return self
|
|
380
|
+
|
|
381
|
+
def __exit__(self, *_: Any) -> None:
|
|
382
|
+
self.close()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opennous
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for Nous — GTM data infrastructure for agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://opennous.cloud
|
|
7
|
+
Project-URL: Repository, https://github.com/bennetglinder1/nous
|
|
8
|
+
Project-URL: Documentation, https://docs.opennous.cloud
|
|
9
|
+
Keywords: nous,opennous,ai,agents,crm,memory,sdk,mcp
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: httpx>=0.27.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.27.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
opennous
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "opennous"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Official Python SDK for Nous — GTM data infrastructure for agents"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
keywords = ["nous", "opennous", "ai", "agents", "crm", "memory", "sdk", "mcp"]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.27.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://opennous.cloud"
|
|
18
|
+
Repository = "https://github.com/bennetglinder1/nous"
|
|
19
|
+
Documentation = "https://docs.opennous.cloud"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["."]
|
|
23
|
+
include = ["opennous*"]
|
opennous-1.0.0/setup.cfg
ADDED