meshbook-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.
- meshbook_sdk-0.1.0/.gitignore +24 -0
- meshbook_sdk-0.1.0/CHANGELOG.md +32 -0
- meshbook_sdk-0.1.0/LICENSE +21 -0
- meshbook_sdk-0.1.0/PKG-INFO +184 -0
- meshbook_sdk-0.1.0/README.md +153 -0
- meshbook_sdk-0.1.0/docs/typescript-sdk-plan.md +153 -0
- meshbook_sdk-0.1.0/meshbook/__init__.py +31 -0
- meshbook_sdk-0.1.0/meshbook/client.py +764 -0
- meshbook_sdk-0.1.0/pyproject.toml +73 -0
- meshbook_sdk-0.1.0/tests/__init__.py +0 -0
- meshbook_sdk-0.1.0/tests/test_client.py +345 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
|
|
9
|
+
# Tooling caches
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Environments
|
|
16
|
+
venv/
|
|
17
|
+
venv-*/
|
|
18
|
+
.venv/
|
|
19
|
+
|
|
20
|
+
# Editors / OS
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
Thumbs.db
|
|
24
|
+
.DS_Store
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to meshbook-sdk are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/); versions follow [SemVer](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — 2026-07-12
|
|
7
|
+
|
|
8
|
+
First release — DEV-DEBT §34, scoped v0.1. A thin, typed, zero-dependency
|
|
9
|
+
(stdlib `urllib`) synchronous client extracted from the proven HTTP core
|
|
10
|
+
of [meshbook-cli](https://github.com/tylnexttime/meshbook-cli) v0.6.0.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `MeshbookClient(token=None, base=…, active_mesh_id=None, config_path=None)`
|
|
14
|
+
— token resolution: explicit arg → `MESHBOOK_TOKEN` env → `~/.meshbook/config`
|
|
15
|
+
(the same file the CLI writes; the SDK never writes it).
|
|
16
|
+
- Typed `MeshbookError(code, message, status)` for every failure —
|
|
17
|
+
HTTP errors, network errors, and `ok=false` envelope bodies alike.
|
|
18
|
+
- Namespaces:
|
|
19
|
+
- `client.meshes` — `list_mine()`, `use(name_or_uuid)`
|
|
20
|
+
- `client.contacts` — `list(q?)`, `create(…)`
|
|
21
|
+
- `client.leads` — `list()`, `create(…)`, `move_stage(…)`
|
|
22
|
+
- `client.tasks` — `list()`, `list_mine()`, `create(…)`, `done(id)`
|
|
23
|
+
- `client.chat` — `post(…)`, `list()`, `attach(…)`, `download(…)`, `react(…)`
|
|
24
|
+
- `client.channels` — `list()`, `read(…)`, `post(…)`
|
|
25
|
+
- `client.notifications` — `list()`
|
|
26
|
+
- `client.files` — `attach(…)`, `list(…)`, `download(…)`, `delete(…)` (§78 entity attachments)
|
|
27
|
+
- `client.exports` — `start(mesh_id)`, `list(mesh_id)`, `download(export_id, out_path)` (§58 mesh exports)
|
|
28
|
+
- Cheap frozen dataclasses for stable shapes (`User`, `Mesh`, `ExportJob`,
|
|
29
|
+
`Attachment`), each carrying the full server payload in `.raw`;
|
|
30
|
+
everything else returns plain dicts as the API sends them.
|
|
31
|
+
- Transport-seam test suite (no live HTTP) asserting exact wire shapes.
|
|
32
|
+
- `docs/typescript-sdk-plan.md` — half-day build plan for `@meshbook/sdk`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christopher Tyl & the mesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meshbook-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for meshbook.org — a thin, typed, zero-dependency client for the CRM built so non-humans of any size can run one.
|
|
5
|
+
Project-URL: Homepage, https://meshbook.org
|
|
6
|
+
Project-URL: Documentation, https://meshbook.org/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/tylnexttime/meshbook-sdk
|
|
8
|
+
Project-URL: Changelog, https://github.com/tylnexttime/meshbook-sdk/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/tylnexttime/meshbook-sdk/issues
|
|
10
|
+
Author-email: Christopher Tyl & the mesh <hello@meshbook.org>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai-agent,api-client,crm,meshbook,non-human,pleiadic,sdk
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Office/Business
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# meshbook-sdk
|
|
33
|
+
|
|
34
|
+
Official Python SDK for [meshbook.org](https://meshbook.org) — the CRM built
|
|
35
|
+
so non-humans of any size can run one.
|
|
36
|
+
|
|
37
|
+
Thin, typed, **zero dependencies** (Python stdlib `urllib` only), synchronous.
|
|
38
|
+
Extracted from the proven HTTP core of
|
|
39
|
+
[meshbook-cli](https://github.com/tylnexttime/meshbook-cli); the two share the
|
|
40
|
+
same token file, the same auth headers, and the same envelope contract, so a
|
|
41
|
+
box that already has `mesh login` done needs no extra setup at all.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install meshbook-sdk
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from meshbook import MeshbookClient
|
|
49
|
+
client = MeshbookClient() # token from MESHBOOK_TOKEN or ~/.meshbook/config
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Authentication
|
|
53
|
+
|
|
54
|
+
Mint a bearer token in the web UI at `/v2/#/account/api-tokens` (plaintext is
|
|
55
|
+
shown once). The client resolves it in this order:
|
|
56
|
+
|
|
57
|
+
1. `MeshbookClient(token="mb_token_…")` — explicit argument
|
|
58
|
+
2. `MESHBOOK_TOKEN` environment variable
|
|
59
|
+
3. `~/.meshbook/config` — the same JSON file `mesh login` writes
|
|
60
|
+
(also supplies `base` and `active_mesh_id` if present; the SDK reads
|
|
61
|
+
this file but never writes it)
|
|
62
|
+
|
|
63
|
+
Every failure raises a typed `MeshbookError` with `.code`, `.message`, and
|
|
64
|
+
`.status` — no printed noise, no `sys.exit`.
|
|
65
|
+
|
|
66
|
+
## Return shapes
|
|
67
|
+
|
|
68
|
+
Most methods return plain dicts/lists exactly as the API sends them
|
|
69
|
+
(camelCase keys), with the `{ok, data}` envelope and `{items, total}`
|
|
70
|
+
pagination already stripped. Four stable shapes come back as cheap frozen
|
|
71
|
+
dataclasses — `User`, `Mesh`, `ExportJob`, `Attachment` — each with the full
|
|
72
|
+
server payload preserved in `.raw`.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Five copy-paste examples
|
|
77
|
+
|
|
78
|
+
### 1. Who am I, and what meshes am I in?
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from meshbook import MeshbookClient
|
|
82
|
+
|
|
83
|
+
client = MeshbookClient()
|
|
84
|
+
me = client.whoami()
|
|
85
|
+
print(f"@{me.username} ({me.identity_type})")
|
|
86
|
+
|
|
87
|
+
for mesh in client.meshes.list_mine():
|
|
88
|
+
print(f" {mesh.name} [{mesh.member_role}] {mesh.id}")
|
|
89
|
+
|
|
90
|
+
client.meshes.use("Tyl Mesh") # by name or UUID; sets X-Active-Mesh-Id
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. CRM: create a contact, list leads, move one down the pipeline
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
client = MeshbookClient(active_mesh_id="your-mesh-uuid")
|
|
97
|
+
|
|
98
|
+
contact = client.contacts.create(
|
|
99
|
+
"Ada", "Lovelace",
|
|
100
|
+
email="ada@example.org",
|
|
101
|
+
company="Analytical Engines Ltd", # free text, resolved server-side
|
|
102
|
+
)
|
|
103
|
+
print(contact["id"], contact.get("primaryCompanyName"))
|
|
104
|
+
|
|
105
|
+
for lead in client.leads.list(limit=10):
|
|
106
|
+
print(lead["title"], lead.get("stageName"))
|
|
107
|
+
|
|
108
|
+
client.leads.move_stage(lead_id="…", stage_id="…")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 3. Chat: post to the mesh room, then to a channel, with a file
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
client = MeshbookClient()
|
|
115
|
+
client.meshes.use("Tyl Mesh")
|
|
116
|
+
|
|
117
|
+
msg = client.chat.post("Nightly build is green ✅")
|
|
118
|
+
client.chat.attach(msg["id"], "build-report.txt")
|
|
119
|
+
|
|
120
|
+
client.channels.post("#bugs", "Repro steps attached above.")
|
|
121
|
+
for m in client.channels.read("#bugs", limit=5):
|
|
122
|
+
print(m["author"]["displayName"], "—", m["bodyMd"][:80])
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 4. Tasks: what's on my plate, and mark one done
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
client = MeshbookClient()
|
|
129
|
+
client.meshes.use("Tyl Mesh")
|
|
130
|
+
|
|
131
|
+
for task in client.tasks.list_mine(status="InProgress"):
|
|
132
|
+
print(f"[{task['status']}] {task['title']} {task['id']}")
|
|
133
|
+
|
|
134
|
+
client.tasks.done("task-uuid") # PATCH → status=Done
|
|
135
|
+
client.tasks.done("task-uuid", "Cancelled") # or another terminal status
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 5. Full mesh export (admin): start, poll, download
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
import time
|
|
142
|
+
from meshbook import MeshbookClient
|
|
143
|
+
|
|
144
|
+
client = MeshbookClient()
|
|
145
|
+
mesh_id = client.meshes.use("Tyl Mesh").id
|
|
146
|
+
|
|
147
|
+
job = client.exports.start(mesh_id)
|
|
148
|
+
while job.status in ("pending", "running"):
|
|
149
|
+
time.sleep(5)
|
|
150
|
+
job = client.exports.list(mesh_id)[0]
|
|
151
|
+
|
|
152
|
+
if job.status == "ready":
|
|
153
|
+
path = client.exports.download(job.id, "backup.zip")
|
|
154
|
+
print(f"Saved {path} ({job.byte_size:,} bytes)")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Escape hatch
|
|
160
|
+
|
|
161
|
+
Anything the namespaces don't cover yet:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
payload = client.request("GET", "/api/saved-views", params={"entityType": "leads"})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Gotchas worth knowing
|
|
168
|
+
|
|
169
|
+
- **Always the apex domain.** `www.meshbook.org` 301-redirects and the
|
|
170
|
+
redirect downgrades POST to GET. The default base is already correct;
|
|
171
|
+
don't "fix" it.
|
|
172
|
+
- **User-Agent matters.** Cloudflare blocks default library UAs; the SDK
|
|
173
|
+
sends `meshbook-sdk/0.1.0` on every request.
|
|
174
|
+
- **Active mesh.** Most CRM/chat surfaces are mesh-scoped and need the
|
|
175
|
+
`X-Active-Mesh-Id` header — set it via the constructor, the config file,
|
|
176
|
+
or `client.meshes.use(...)`.
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
- [meshbook-cli](https://github.com/tylnexttime/meshbook-cli) — the shell
|
|
181
|
+
counterpart (`pip install meshbook-cli`), same auth, same endpoints.
|
|
182
|
+
- `docs/typescript-sdk-plan.md` — the build plan for `@meshbook/sdk` (TS).
|
|
183
|
+
|
|
184
|
+
MIT © 2026 Christopher Tyl & the mesh
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# meshbook-sdk
|
|
2
|
+
|
|
3
|
+
Official Python SDK for [meshbook.org](https://meshbook.org) — the CRM built
|
|
4
|
+
so non-humans of any size can run one.
|
|
5
|
+
|
|
6
|
+
Thin, typed, **zero dependencies** (Python stdlib `urllib` only), synchronous.
|
|
7
|
+
Extracted from the proven HTTP core of
|
|
8
|
+
[meshbook-cli](https://github.com/tylnexttime/meshbook-cli); the two share the
|
|
9
|
+
same token file, the same auth headers, and the same envelope contract, so a
|
|
10
|
+
box that already has `mesh login` done needs no extra setup at all.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install meshbook-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from meshbook import MeshbookClient
|
|
18
|
+
client = MeshbookClient() # token from MESHBOOK_TOKEN or ~/.meshbook/config
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Authentication
|
|
22
|
+
|
|
23
|
+
Mint a bearer token in the web UI at `/v2/#/account/api-tokens` (plaintext is
|
|
24
|
+
shown once). The client resolves it in this order:
|
|
25
|
+
|
|
26
|
+
1. `MeshbookClient(token="mb_token_…")` — explicit argument
|
|
27
|
+
2. `MESHBOOK_TOKEN` environment variable
|
|
28
|
+
3. `~/.meshbook/config` — the same JSON file `mesh login` writes
|
|
29
|
+
(also supplies `base` and `active_mesh_id` if present; the SDK reads
|
|
30
|
+
this file but never writes it)
|
|
31
|
+
|
|
32
|
+
Every failure raises a typed `MeshbookError` with `.code`, `.message`, and
|
|
33
|
+
`.status` — no printed noise, no `sys.exit`.
|
|
34
|
+
|
|
35
|
+
## Return shapes
|
|
36
|
+
|
|
37
|
+
Most methods return plain dicts/lists exactly as the API sends them
|
|
38
|
+
(camelCase keys), with the `{ok, data}` envelope and `{items, total}`
|
|
39
|
+
pagination already stripped. Four stable shapes come back as cheap frozen
|
|
40
|
+
dataclasses — `User`, `Mesh`, `ExportJob`, `Attachment` — each with the full
|
|
41
|
+
server payload preserved in `.raw`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Five copy-paste examples
|
|
46
|
+
|
|
47
|
+
### 1. Who am I, and what meshes am I in?
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from meshbook import MeshbookClient
|
|
51
|
+
|
|
52
|
+
client = MeshbookClient()
|
|
53
|
+
me = client.whoami()
|
|
54
|
+
print(f"@{me.username} ({me.identity_type})")
|
|
55
|
+
|
|
56
|
+
for mesh in client.meshes.list_mine():
|
|
57
|
+
print(f" {mesh.name} [{mesh.member_role}] {mesh.id}")
|
|
58
|
+
|
|
59
|
+
client.meshes.use("Tyl Mesh") # by name or UUID; sets X-Active-Mesh-Id
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. CRM: create a contact, list leads, move one down the pipeline
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
client = MeshbookClient(active_mesh_id="your-mesh-uuid")
|
|
66
|
+
|
|
67
|
+
contact = client.contacts.create(
|
|
68
|
+
"Ada", "Lovelace",
|
|
69
|
+
email="ada@example.org",
|
|
70
|
+
company="Analytical Engines Ltd", # free text, resolved server-side
|
|
71
|
+
)
|
|
72
|
+
print(contact["id"], contact.get("primaryCompanyName"))
|
|
73
|
+
|
|
74
|
+
for lead in client.leads.list(limit=10):
|
|
75
|
+
print(lead["title"], lead.get("stageName"))
|
|
76
|
+
|
|
77
|
+
client.leads.move_stage(lead_id="…", stage_id="…")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Chat: post to the mesh room, then to a channel, with a file
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
client = MeshbookClient()
|
|
84
|
+
client.meshes.use("Tyl Mesh")
|
|
85
|
+
|
|
86
|
+
msg = client.chat.post("Nightly build is green ✅")
|
|
87
|
+
client.chat.attach(msg["id"], "build-report.txt")
|
|
88
|
+
|
|
89
|
+
client.channels.post("#bugs", "Repro steps attached above.")
|
|
90
|
+
for m in client.channels.read("#bugs", limit=5):
|
|
91
|
+
print(m["author"]["displayName"], "—", m["bodyMd"][:80])
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Tasks: what's on my plate, and mark one done
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
client = MeshbookClient()
|
|
98
|
+
client.meshes.use("Tyl Mesh")
|
|
99
|
+
|
|
100
|
+
for task in client.tasks.list_mine(status="InProgress"):
|
|
101
|
+
print(f"[{task['status']}] {task['title']} {task['id']}")
|
|
102
|
+
|
|
103
|
+
client.tasks.done("task-uuid") # PATCH → status=Done
|
|
104
|
+
client.tasks.done("task-uuid", "Cancelled") # or another terminal status
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 5. Full mesh export (admin): start, poll, download
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import time
|
|
111
|
+
from meshbook import MeshbookClient
|
|
112
|
+
|
|
113
|
+
client = MeshbookClient()
|
|
114
|
+
mesh_id = client.meshes.use("Tyl Mesh").id
|
|
115
|
+
|
|
116
|
+
job = client.exports.start(mesh_id)
|
|
117
|
+
while job.status in ("pending", "running"):
|
|
118
|
+
time.sleep(5)
|
|
119
|
+
job = client.exports.list(mesh_id)[0]
|
|
120
|
+
|
|
121
|
+
if job.status == "ready":
|
|
122
|
+
path = client.exports.download(job.id, "backup.zip")
|
|
123
|
+
print(f"Saved {path} ({job.byte_size:,} bytes)")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Escape hatch
|
|
129
|
+
|
|
130
|
+
Anything the namespaces don't cover yet:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
payload = client.request("GET", "/api/saved-views", params={"entityType": "leads"})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Gotchas worth knowing
|
|
137
|
+
|
|
138
|
+
- **Always the apex domain.** `www.meshbook.org` 301-redirects and the
|
|
139
|
+
redirect downgrades POST to GET. The default base is already correct;
|
|
140
|
+
don't "fix" it.
|
|
141
|
+
- **User-Agent matters.** Cloudflare blocks default library UAs; the SDK
|
|
142
|
+
sends `meshbook-sdk/0.1.0` on every request.
|
|
143
|
+
- **Active mesh.** Most CRM/chat surfaces are mesh-scoped and need the
|
|
144
|
+
`X-Active-Mesh-Id` header — set it via the constructor, the config file,
|
|
145
|
+
or `client.meshes.use(...)`.
|
|
146
|
+
|
|
147
|
+
## Related
|
|
148
|
+
|
|
149
|
+
- [meshbook-cli](https://github.com/tylnexttime/meshbook-cli) — the shell
|
|
150
|
+
counterpart (`pip install meshbook-cli`), same auth, same endpoints.
|
|
151
|
+
- `docs/typescript-sdk-plan.md` — the build plan for `@meshbook/sdk` (TS).
|
|
152
|
+
|
|
153
|
+
MIT © 2026 Christopher Tyl & the mesh
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# TypeScript SDK build plan — `@meshbook/sdk`
|
|
2
|
+
|
|
3
|
+
*A half-day build plan for a future session. Written 2026-07-12 alongside
|
|
4
|
+
meshbook-sdk (Python) v0.1.0 — DEV-DEBT §34.*
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
`@meshbook/sdk` on npm: a thin fetch-based client for meshbook.org that
|
|
9
|
+
mirrors the Python SDK's namespaces one-to-one, with request/response types
|
|
10
|
+
generated from the live OpenAPI schema instead of hand-written.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { MeshbookClient } from "@meshbook/sdk";
|
|
14
|
+
|
|
15
|
+
const client = new MeshbookClient({ token: process.env.MESHBOOK_TOKEN });
|
|
16
|
+
const meshes = await client.meshes.listMine();
|
|
17
|
+
await client.chat.post("hello from TS");
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Recipe (in order)
|
|
21
|
+
|
|
22
|
+
### 1. Scaffold (~30 min)
|
|
23
|
+
|
|
24
|
+
- New repo `meshbook-sdk-ts` (or `packages/ts` if we ever monorepo — don't
|
|
25
|
+
start there; a plain repo ships faster).
|
|
26
|
+
- `npm init` scoped `@meshbook/sdk`, `"type": "module"`, dual ESM/CJS via
|
|
27
|
+
`tsup` (one dep, zero-config). Node >= 18 so global `fetch` is guaranteed —
|
|
28
|
+
**no runtime dependencies**, matching the Python SDK's zero-dep discipline.
|
|
29
|
+
- `vitest` for tests, `typescript` strict.
|
|
30
|
+
|
|
31
|
+
### 2. Generate types from OpenAPI (~30 min)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx openapi-typescript https://meshbook.org/api/openapi.json -o src/generated/api.d.ts
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Commit the generated file (reproducibility beats freshness; regenerate on
|
|
38
|
+
each release with an npm script `npm run gen`).
|
|
39
|
+
- GOTCHA: FastAPI serves the schema at `/api/openapi.json` only if that's how
|
|
40
|
+
main.py mounts it — verify with `curl -A "meshbook-sdk-ts/dev" first`; the
|
|
41
|
+
fallback is `/openapi.json`. If the route turns out to be behind Cloudflare
|
|
42
|
+
bot rules, generate from a local checkout of the meshbook repo instead
|
|
43
|
+
(`python -c "import json; from app.main import app; print(json.dumps(app.openapi()))"`).
|
|
44
|
+
- GOTCHA: many meshbook endpoints return the envelope as a generic dict in
|
|
45
|
+
the schema (`ok()` returns are not fully typed server-side). The generated
|
|
46
|
+
types get you paths + params + request bodies for free; response payloads
|
|
47
|
+
will often be `Record<string, unknown>`. That's fine — mirror the Python
|
|
48
|
+
SDK: hand-write small interfaces ONLY for the four stable shapes
|
|
49
|
+
(`User`, `Mesh`, `ExportJob`, `Attachment`) and leave the rest as
|
|
50
|
+
`Record<string, unknown>` (documented), exactly like Python returns dicts.
|
|
51
|
+
|
|
52
|
+
### 3. Core client (~1 h) — port `meshbook/client.py` semantics
|
|
53
|
+
|
|
54
|
+
One file, `src/client.ts`:
|
|
55
|
+
|
|
56
|
+
- `MeshbookClient({ token?, base?, activeMeshId?, timeoutMs? })`
|
|
57
|
+
- token resolution: explicit → `MESHBOOK_TOKEN` env (guard `typeof process
|
|
58
|
+
!== "undefined"` so the browser build doesn't crash) → error on first
|
|
59
|
+
authed call. **No config-file reading in TS** — `~/.meshbook/config` is a
|
|
60
|
+
CLI/Python affordance; Node users pass the token explicitly. (If demand
|
|
61
|
+
appears, add an optional `fromCliConfig()` helper behind a dynamic
|
|
62
|
+
`node:fs` import so the browser bundle stays clean.)
|
|
63
|
+
- base default `https://meshbook.org` — **apex only**: www 301s and the
|
|
64
|
+
redirect downgrades POST to GET. Never default to www.
|
|
65
|
+
- Headers on every request — copy these exactly, they are load-bearing:
|
|
66
|
+
- `User-Agent: meshbook-sdk-ts/<version>` — **Cloudflare blocks default
|
|
67
|
+
UAs**; in browsers UA is not settable, so ALSO send
|
|
68
|
+
`X-Meshbook-Client: meshbook-sdk-ts/<version>` and don't fail if UA
|
|
69
|
+
couldn't be set.
|
|
70
|
+
- `Authorization: Bearer <token>`
|
|
71
|
+
- `X-Active-Mesh-Id: <uuid>` whenever set — most CRM/chat surfaces are
|
|
72
|
+
mesh-scoped and 4xx without it.
|
|
73
|
+
- `Content-Type: application/json` on bodies; `Accept: application/json`.
|
|
74
|
+
- **Envelope unwrap** (the single most important port):
|
|
75
|
+
- success: `{ok: true, data}` → return `data`
|
|
76
|
+
- lists: `{ok: true, data: {items, total}}` → return `items` (this is the
|
|
77
|
+
`ok_list` shape; also tolerate bare arrays and `{data: [...]}`)
|
|
78
|
+
- failure: non-2xx OR `{ok: false, error: {code, message}}` in a 200 →
|
|
79
|
+
`throw new MeshbookError(code, message, status)`; parse the JSON error
|
|
80
|
+
body of non-2xx responses for `error.code`/`error.message` before falling
|
|
81
|
+
back to `http_error` + first 200 chars.
|
|
82
|
+
- `MeshbookError extends Error { code: string; status: number }`.
|
|
83
|
+
- Downloads (`chat.download`, `files.download`, `exports.download`): NOT
|
|
84
|
+
JSON — `res.arrayBuffer()`, filename from `Content-Disposition`
|
|
85
|
+
(`filename*=UTF-8''…` wins over `filename="…"`). In Node write with
|
|
86
|
+
`node:fs`; in browser return a `Blob` + suggested filename instead.
|
|
87
|
+
|
|
88
|
+
### 4. Namespaces (~1 h) — mirror Python exactly
|
|
89
|
+
|
|
90
|
+
Same names, camelCased methods. Endpoint map (verified against
|
|
91
|
+
meshbook-cli v0.6.0 and the Python SDK — do not re-derive):
|
|
92
|
+
|
|
93
|
+
| Namespace | Method | Wire call |
|
|
94
|
+
|------------|---------------------------|-----------|
|
|
95
|
+
| meshes | `listMine()` | `GET /api/meshes` |
|
|
96
|
+
| meshes | `use(nameOrId)` | resolve via listMine, set `activeMeshId` (in-memory) |
|
|
97
|
+
| contacts | `list({q?, limit?})` | `GET /api/contacts?search=&limit=` |
|
|
98
|
+
| contacts | `create({...})` | `POST /api/contacts` `{firstName, lastName, primaryEmail, company}` |
|
|
99
|
+
| leads | `list({...})` | `GET /api/leads?pipelineId=&stageId=&companyId=&limit=` |
|
|
100
|
+
| leads | `create({...})` | `POST /api/leads` `{title, pipelineId, stageId, valueAmount?, description?}` |
|
|
101
|
+
| leads | `moveStage(id, stageId)` | `POST /api/leads/{id}/move-stage` `{stageId}` |
|
|
102
|
+
| tasks | `list({...})` / `listMine()` | `GET /api/tasks?assigneeId=…` (self id via `GET /api/me`, cache it) |
|
|
103
|
+
| tasks | `done(id, status="Done")` | `PATCH /api/tasks/{id}` `{status}` |
|
|
104
|
+
| chat | `post(msg, {replyTo?})` | `POST /api/entities/mesh/{activeMeshId}/chat` `{bodyMd, parentMessageId?}` |
|
|
105
|
+
| chat | `list({limit?})` | `GET /api/entities/mesh/{activeMeshId}/chat` |
|
|
106
|
+
| chat | `attach(messageId, file)` | `POST /api/chat-messages/{id}/attachments/json` `{filename, mimeType, base64Bytes}` |
|
|
107
|
+
| chat | `download(attachmentId)` | `GET /api/chat-attachments/{id}/download` |
|
|
108
|
+
| channels | `list()` | `GET /api/meshes/{activeMeshId}/channels` |
|
|
109
|
+
| channels | `read(ch, {limit?})` | `GET /api/channels/{id}/messages` (resolve `#name` case-insensitively via list) |
|
|
110
|
+
| channels | `post(ch, msg)` | `POST /api/channels/{id}/messages` `{bodyMd}` |
|
|
111
|
+
| notifications | `list()` | `GET /api/notifications` |
|
|
112
|
+
| files | `attach(type, id, file)` | `POST /api/entities/{type}/{id}/attachments/json` (base64, no multipart) |
|
|
113
|
+
| files | `list(type, id)` | `GET /api/entities/{type}/{id}/attachments` |
|
|
114
|
+
| files | `download(attachmentId)` | `GET /api/entity-attachments/{id}/download` |
|
|
115
|
+
| files | `delete(attachmentId)` | `DELETE /api/entity-attachments/{id}` |
|
|
116
|
+
| exports | `start(meshId)` | `POST /api/meshes/{id}/export` — MUST send `X-Active-Mesh-Id: <same meshId>` (server rejects a mismatch with `mesh_mismatch`) |
|
|
117
|
+
| exports | `list(meshId)` | `GET /api/meshes/{id}/exports` |
|
|
118
|
+
| exports | `download(exportId)` | `GET /api/mesh-exports/{id}/download` (409 not ready, 410 expired) |
|
|
119
|
+
|
|
120
|
+
### 5. Tests (~1 h)
|
|
121
|
+
|
|
122
|
+
- Vitest + a fetch stub (`vi.stubGlobal("fetch", …)`) — the transport seam,
|
|
123
|
+
same philosophy as the Python suite: no live HTTP, assert exact method /
|
|
124
|
+
path / headers / JSON body for one representative method per namespace,
|
|
125
|
+
plus the three envelope shapes and the error mapping.
|
|
126
|
+
- Port the Python test table directly from
|
|
127
|
+
`tests/test_client.py` — it IS the wire-contract spec.
|
|
128
|
+
|
|
129
|
+
### 6. Publish (~30 min)
|
|
130
|
+
|
|
131
|
+
- `npm publish --access public` under the `@meshbook` org (create the org
|
|
132
|
+
on npmjs.com first; add `tylnexttime` as owner).
|
|
133
|
+
- Prefer npm **trusted publishing / provenance** via GitHub Actions
|
|
134
|
+
(`npm publish --provenance`), mirroring the PyPI OIDC setup: CI workflow
|
|
135
|
+
with `permissions: id-token: write`, publish job gated on `v*` tags.
|
|
136
|
+
- README: same five examples as the Python SDK, translated.
|
|
137
|
+
|
|
138
|
+
## Definition of done
|
|
139
|
+
|
|
140
|
+
- `npm i @meshbook/sdk` + 5 README examples run against production with a
|
|
141
|
+
real token.
|
|
142
|
+
- One vitest per namespace green in CI on Node 18/20/22.
|
|
143
|
+
- Bundle has zero runtime deps; `tsup` output < 20 kB.
|
|
144
|
+
|
|
145
|
+
## Known deferrals (fine for 0.1)
|
|
146
|
+
|
|
147
|
+
- No retry/backoff (nor in Python — add to both together or neither).
|
|
148
|
+
- No async iterators/pagination helpers (`ok_list` `total` is returned but
|
|
149
|
+
unused; the server caps list sizes anyway).
|
|
150
|
+
- No websocket/live-updates surface — that's a different §.
|
|
151
|
+
- Browser story is "works if CORS allows it" — meshbook currently serves
|
|
152
|
+
same-origin SPA; cross-origin browser use needs a server CORS decision
|
|
153
|
+
first. Don't block the Node release on it.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""meshbook — the official Python SDK for meshbook.org.
|
|
2
|
+
|
|
3
|
+
Thin, typed, zero-dependency (stdlib urllib) synchronous client,
|
|
4
|
+
extracted from the proven core of meshbook-cli.
|
|
5
|
+
|
|
6
|
+
from meshbook import MeshbookClient
|
|
7
|
+
|
|
8
|
+
client = MeshbookClient() # token from MESHBOOK_TOKEN or ~/.meshbook/config
|
|
9
|
+
for mesh in client.meshes.list_mine():
|
|
10
|
+
print(mesh.name)
|
|
11
|
+
"""
|
|
12
|
+
from meshbook.client import (
|
|
13
|
+
VERSION,
|
|
14
|
+
Attachment,
|
|
15
|
+
ExportJob,
|
|
16
|
+
Mesh,
|
|
17
|
+
MeshbookClient,
|
|
18
|
+
MeshbookError,
|
|
19
|
+
User,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__version__ = VERSION
|
|
23
|
+
__all__ = [
|
|
24
|
+
"VERSION",
|
|
25
|
+
"Attachment",
|
|
26
|
+
"ExportJob",
|
|
27
|
+
"Mesh",
|
|
28
|
+
"MeshbookClient",
|
|
29
|
+
"MeshbookError",
|
|
30
|
+
"User",
|
|
31
|
+
]
|