better-notion 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- better_notion/_sdk/client.py +106 -3
- better_notion/_sdk/plugins.py +180 -0
- better_notion/plugins/base.py +99 -1
- better_notion/plugins/loader.py +51 -1
- better_notion/plugins/official/agents.py +150 -5
- better_notion/plugins/official/agents_cli.py +1767 -0
- better_notion/plugins/official/agents_sdk/__init__.py +30 -0
- better_notion/plugins/official/agents_sdk/managers.py +973 -0
- better_notion/plugins/official/agents_sdk/models.py +2256 -0
- better_notion/plugins/official/agents_sdk/plugin.py +146 -0
- {better_notion-1.0.0.dist-info → better_notion-1.1.0.dist-info}/METADATA +2 -2
- {better_notion-1.0.0.dist-info → better_notion-1.1.0.dist-info}/RECORD +15 -9
- {better_notion-1.0.0.dist-info → better_notion-1.1.0.dist-info}/WHEEL +0 -0
- {better_notion-1.0.0.dist-info → better_notion-1.1.0.dist-info}/entry_points.txt +0 -0
- {better_notion-1.0.0.dist-info → better_notion-1.1.0.dist-info}/licenses/LICENSE +0 -0
better_notion/_sdk/client.py
CHANGED
|
@@ -99,6 +99,11 @@ class NotionClient:
|
|
|
99
99
|
self._comment_cache: Cache[object] = Cache()
|
|
100
100
|
# No cache for Block (too many)
|
|
101
101
|
|
|
102
|
+
# Plugin-managed resources (for SDK plugin system)
|
|
103
|
+
self._plugin_caches: dict[str, Cache[object]] = {}
|
|
104
|
+
self._plugin_managers: dict[str, object] = {}
|
|
105
|
+
self._plugin_models: dict[str, type] = {}
|
|
106
|
+
|
|
102
107
|
# Search cache
|
|
103
108
|
self._search_cache: dict[str, list[object]] = {}
|
|
104
109
|
|
|
@@ -259,10 +264,92 @@ class NotionClient:
|
|
|
259
264
|
|
|
260
265
|
return results
|
|
261
266
|
|
|
267
|
+
# ===== SDK PLUGIN SYSTEM =====
|
|
268
|
+
|
|
269
|
+
def register_sdk_plugin(
|
|
270
|
+
self,
|
|
271
|
+
models: dict[str, type] | None = None,
|
|
272
|
+
caches: dict[str, "Cache"] | None = None,
|
|
273
|
+
managers: dict[str, object] | None = None,
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Register an SDK plugin's resources with the client.
|
|
276
|
+
|
|
277
|
+
This method allows plugins to register custom models, caches, and
|
|
278
|
+
managers without modifying the core SDK.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
models: Dict mapping model names to model classes
|
|
282
|
+
caches: Dict mapping cache names to Cache instances
|
|
283
|
+
managers: Dict mapping manager names to manager instances
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> client.register_sdk_plugin(
|
|
287
|
+
... models={"Organization": Organization},
|
|
288
|
+
... caches={"organizations": Cache()},
|
|
289
|
+
... managers={"organizations": OrganizationManager(client)},
|
|
290
|
+
... )
|
|
291
|
+
"""
|
|
292
|
+
if models:
|
|
293
|
+
self._plugin_models.update(models)
|
|
294
|
+
|
|
295
|
+
if caches:
|
|
296
|
+
self._plugin_caches.update(caches)
|
|
297
|
+
|
|
298
|
+
if managers:
|
|
299
|
+
self._plugin_managers.update(managers)
|
|
300
|
+
|
|
301
|
+
def plugin_cache(self, name: str) -> "Cache | None":
|
|
302
|
+
"""Access a plugin-registered cache.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
name: Cache name (e.g., "organizations")
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Cache instance if found, None otherwise
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
>>> cache = client.plugin_cache("organizations")
|
|
312
|
+
>>> if cache and org_id in cache:
|
|
313
|
+
... org = cache[org_id]
|
|
314
|
+
"""
|
|
315
|
+
return self._plugin_caches.get(name)
|
|
316
|
+
|
|
317
|
+
def plugin_manager(self, name: str) -> object | None:
|
|
318
|
+
"""Access a plugin-registered manager.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
name: Manager name (e.g., "organizations")
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Manager instance if found, None otherwise
|
|
325
|
+
|
|
326
|
+
Example:
|
|
327
|
+
>>> orgs_mgr = client.plugin_manager("organizations")
|
|
328
|
+
>>> if orgs_mgr:
|
|
329
|
+
... orgs = await orgs_mgr.list()
|
|
330
|
+
"""
|
|
331
|
+
return self._plugin_managers.get(name)
|
|
332
|
+
|
|
333
|
+
def plugin_model(self, name: str) -> type | None:
|
|
334
|
+
"""Access a plugin-registered model class.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
name: Model name (e.g., "Organization")
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
Model class if found, None otherwise
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
>>> OrgClass = client.plugin_model("Organization")
|
|
344
|
+
>>> if OrgClass:
|
|
345
|
+
... org = await OrgClass.get(org_id, client=client)
|
|
346
|
+
"""
|
|
347
|
+
return self._plugin_models.get(name)
|
|
348
|
+
|
|
262
349
|
# ===== CACHE MANAGEMENT =====
|
|
263
350
|
|
|
264
351
|
def clear_all_caches(self) -> None:
|
|
265
|
-
"""Clear all caches.
|
|
352
|
+
"""Clear all caches including plugin caches.
|
|
266
353
|
|
|
267
354
|
Example:
|
|
268
355
|
>>> client.clear_all_caches()
|
|
@@ -272,8 +359,12 @@ class NotionClient:
|
|
|
272
359
|
self._page_cache.clear()
|
|
273
360
|
self._search_cache.clear()
|
|
274
361
|
|
|
362
|
+
# Clear plugin caches
|
|
363
|
+
for cache in self._plugin_caches.values():
|
|
364
|
+
cache.clear()
|
|
365
|
+
|
|
275
366
|
def get_cache_stats(self) -> dict[str, dict[str, Any]]:
|
|
276
|
-
"""Get statistics for all caches.
|
|
367
|
+
"""Get statistics for all caches including plugin caches.
|
|
277
368
|
|
|
278
369
|
Returns:
|
|
279
370
|
Dict with stats for each cache
|
|
@@ -283,10 +374,11 @@ class NotionClient:
|
|
|
283
374
|
>>> print(stats)
|
|
284
375
|
{
|
|
285
376
|
'user_cache': {'hits': 100, 'misses': 5, 'size': 50, 'hit_rate': 0.95},
|
|
377
|
+
'plugin:organizations': {'hits': 50, 'misses': 2, 'size': 10, 'hit_rate': 0.96},
|
|
286
378
|
...
|
|
287
379
|
}
|
|
288
380
|
"""
|
|
289
|
-
|
|
381
|
+
stats = {
|
|
290
382
|
"user_cache": {
|
|
291
383
|
"hits": self._user_cache.stats.hits,
|
|
292
384
|
"misses": self._user_cache.stats.misses,
|
|
@@ -310,6 +402,17 @@ class NotionClient:
|
|
|
310
402
|
}
|
|
311
403
|
}
|
|
312
404
|
|
|
405
|
+
# Add plugin cache stats
|
|
406
|
+
for cache_name, cache in self._plugin_caches.items():
|
|
407
|
+
stats[f"plugin:{cache_name}"] = {
|
|
408
|
+
"hits": cache.stats.hits,
|
|
409
|
+
"misses": cache.stats.misses,
|
|
410
|
+
"size": cache.stats.size,
|
|
411
|
+
"hit_rate": cache.stats.hit_rate
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return stats
|
|
415
|
+
|
|
313
416
|
# ===== CONTEXT MANAGER =====
|
|
314
417
|
|
|
315
418
|
async def __aenter__(self):
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""SDK Plugin System for extending NotionClient functionality.
|
|
2
|
+
|
|
3
|
+
This module provides the protocol interface for SDK-level plugins that need to:
|
|
4
|
+
- Register custom model classes
|
|
5
|
+
- Add dedicated caches to NotionClient
|
|
6
|
+
- Create custom managers
|
|
7
|
+
- Initialize plugin-specific resources
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from better_notion._sdk.cache import Cache
|
|
16
|
+
from better_notion._sdk.client import NotionClient
|
|
17
|
+
from better_notion._sdk.base.entity import BaseEntity
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SDKPluginInterface(Protocol):
|
|
21
|
+
"""
|
|
22
|
+
Protocol for SDK-level plugins that extend NotionClient functionality.
|
|
23
|
+
|
|
24
|
+
SDK plugins can register custom models, caches, and managers with the
|
|
25
|
+
NotionClient, enabling domain-specific extensions without modifying
|
|
26
|
+
the core SDK.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> class AgentsSDKPlugin:
|
|
30
|
+
... def register_models(self) -> dict[str, type[BaseEntity]]:
|
|
31
|
+
... return {"Organization": Organization}
|
|
32
|
+
...
|
|
33
|
+
... def register_caches(self, client: NotionClient) -> dict[str, Cache]:
|
|
34
|
+
... return {"organizations": Cache()}
|
|
35
|
+
...
|
|
36
|
+
... def register_managers(self, client: NotionClient) -> dict[str, Any]:
|
|
37
|
+
... return {"organizations": OrganizationManager(client)}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def register_models(self) -> dict[str, type[BaseEntity]]:
|
|
41
|
+
"""Register custom model classes.
|
|
42
|
+
|
|
43
|
+
Model classes should inherit from BaseEntity and follow the
|
|
44
|
+
autonomous entity pattern (get, create, update methods).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dictionary mapping model names to model classes
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> def register_models(self) -> dict[str, type[BaseEntity]]:
|
|
51
|
+
... return {
|
|
52
|
+
... "Organization": Organization,
|
|
53
|
+
... "Project": Project,
|
|
54
|
+
... "Task": Task,
|
|
55
|
+
... }
|
|
56
|
+
"""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def register_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
|
|
60
|
+
"""Register custom caches with the client.
|
|
61
|
+
|
|
62
|
+
Caches are stored in the client and accessible via
|
|
63
|
+
client.plugin_cache(name). Each cache should have a unique name
|
|
64
|
+
to avoid collisions with other plugins.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
client: NotionClient instance to register caches with
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dictionary mapping cache names to Cache instances
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> def register_caches(self, client) -> dict[str, Cache]:
|
|
74
|
+
... return {
|
|
75
|
+
... "organizations": Cache(),
|
|
76
|
+
... "projects": Cache(),
|
|
77
|
+
... "tasks": Cache(),
|
|
78
|
+
... }
|
|
79
|
+
"""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
def register_managers(self, client: "NotionClient") -> dict[str, Any]:
|
|
83
|
+
"""Register custom managers with the client.
|
|
84
|
+
|
|
85
|
+
Managers are accessible via client.plugin_manager(name).
|
|
86
|
+
They typically provide convenience methods for working with
|
|
87
|
+
the plugin's models.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
client: NotionClient instance
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary mapping manager names to manager instances
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> def register_managers(self, client) -> dict[str, Any]:
|
|
97
|
+
... return {
|
|
98
|
+
... "organizations": OrganizationManager(client),
|
|
99
|
+
... "projects": ProjectManager(client),
|
|
100
|
+
... }
|
|
101
|
+
"""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
def initialize(self, client: "NotionClient") -> None:
|
|
105
|
+
"""Initialize plugin-specific resources.
|
|
106
|
+
|
|
107
|
+
Called after all registrations (models, caches, managers) are
|
|
108
|
+
complete. Use this for setup tasks like loading configuration,
|
|
109
|
+
validating resources, or establishing connections.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
client: NotionClient instance
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> def initialize(self, client) -> None:
|
|
116
|
+
... # Load database IDs from config
|
|
117
|
+
... db_ids = load_workspace_config()
|
|
118
|
+
... client._workspace_config = db_ids
|
|
119
|
+
"""
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
def get_info(self) -> dict[str, Any]:
|
|
123
|
+
"""Return plugin metadata.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Dictionary with plugin information
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
>>> def get_info(self) -> dict[str, Any]:
|
|
130
|
+
... return {
|
|
131
|
+
... "name": "agents-sdk",
|
|
132
|
+
... "version": "1.0.0",
|
|
133
|
+
... "description": "SDK extensions for agents workflow",
|
|
134
|
+
... }
|
|
135
|
+
"""
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class CombinedPluginInterface(Protocol):
|
|
140
|
+
"""
|
|
141
|
+
Protocol for plugins that extend both CLI and SDK.
|
|
142
|
+
|
|
143
|
+
This allows a single plugin class to provide:
|
|
144
|
+
- CLI commands (via register_commands)
|
|
145
|
+
- SDK extensions (via register_models, register_caches, etc.)
|
|
146
|
+
|
|
147
|
+
Example:
|
|
148
|
+
>>> class AgentsPlugin(CombinedPluginInterface):
|
|
149
|
+
... def register_commands(self, app: typer.Typer) -> None:
|
|
150
|
+
... # Register CLI commands
|
|
151
|
+
... pass
|
|
152
|
+
...
|
|
153
|
+
... def register_models(self) -> dict[str, type[BaseEntity]]:
|
|
154
|
+
... # Register SDK models
|
|
155
|
+
... return {"Organization": Organization}
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def register_commands(self, app: Any) -> None:
|
|
159
|
+
"""Register CLI commands with Typer app."""
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
def register_models(self) -> dict[str, type[BaseEntity]]:
|
|
163
|
+
"""Register custom model classes."""
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
def register_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
|
|
167
|
+
"""Register custom caches."""
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
def register_managers(self, client: "NotionClient") -> dict[str, Any]:
|
|
171
|
+
"""Register custom managers."""
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
def initialize(self, client: "NotionClient") -> None:
|
|
175
|
+
"""Initialize plugin resources."""
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
def get_info(self) -> dict[str, Any]:
|
|
179
|
+
"""Return plugin metadata."""
|
|
180
|
+
...
|
better_notion/plugins/base.py
CHANGED
|
@@ -5,14 +5,20 @@ Plugins can extend the CLI by:
|
|
|
5
5
|
1. Adding new commands (CommandPlugin)
|
|
6
6
|
2. Providing data filters/formatters (DataPlugin)
|
|
7
7
|
3. Combining both capabilities
|
|
8
|
+
4. Extending the SDK with models, caches, and managers (SDKPlugin)
|
|
8
9
|
"""
|
|
9
10
|
from __future__ import annotations
|
|
10
11
|
|
|
11
12
|
from abc import ABC, abstractmethod
|
|
12
|
-
from typing import Any, Protocol
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
13
14
|
|
|
14
15
|
import typer
|
|
15
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from better_notion._sdk.cache import Cache
|
|
19
|
+
from better_notion._sdk.client import NotionClient
|
|
20
|
+
from better_notion._sdk.base.entity import BaseEntity
|
|
21
|
+
|
|
16
22
|
|
|
17
23
|
class CommandPlugin(Protocol):
|
|
18
24
|
"""
|
|
@@ -148,6 +154,98 @@ class PluginInterface(ABC):
|
|
|
148
154
|
"""
|
|
149
155
|
return {}
|
|
150
156
|
|
|
157
|
+
# ===== SDK EXTENSION METHODS (OPTIONAL) =====
|
|
158
|
+
|
|
159
|
+
def register_sdk_models(self) -> dict[str, type[BaseEntity]]:
|
|
160
|
+
"""
|
|
161
|
+
Register custom SDK model classes.
|
|
162
|
+
|
|
163
|
+
Plugins can register domain-specific models that extend BaseEntity.
|
|
164
|
+
These models are accessible via client.plugin_model(name).
|
|
165
|
+
|
|
166
|
+
Default implementation returns empty dict.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Dictionary mapping model names to model classes
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> def register_sdk_models(self) -> dict[str, type[BaseEntity]]:
|
|
173
|
+
... return {
|
|
174
|
+
... "Organization": Organization,
|
|
175
|
+
... "Project": Project,
|
|
176
|
+
... }
|
|
177
|
+
"""
|
|
178
|
+
return {}
|
|
179
|
+
|
|
180
|
+
def register_sdk_caches(self, client: "NotionClient") -> dict[str, "Cache"]:
|
|
181
|
+
"""
|
|
182
|
+
Register custom SDK caches.
|
|
183
|
+
|
|
184
|
+
Plugins can register dedicated caches for their models.
|
|
185
|
+
These caches are accessible via client.plugin_cache(name).
|
|
186
|
+
|
|
187
|
+
Default implementation returns empty dict.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
client: NotionClient instance
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary mapping cache names to Cache instances
|
|
194
|
+
|
|
195
|
+
Example:
|
|
196
|
+
>>> def register_sdk_caches(self, client) -> dict[str, Cache]:
|
|
197
|
+
... return {
|
|
198
|
+
... "organizations": Cache(),
|
|
199
|
+
... "projects": Cache(),
|
|
200
|
+
... }
|
|
201
|
+
"""
|
|
202
|
+
return {}
|
|
203
|
+
|
|
204
|
+
def register_sdk_managers(self, client: "NotionClient") -> dict[str, Any]:
|
|
205
|
+
"""
|
|
206
|
+
Register custom SDK managers.
|
|
207
|
+
|
|
208
|
+
Plugins can register managers that provide convenience methods
|
|
209
|
+
for working with their models. Managers are accessible via
|
|
210
|
+
client.plugin_manager(name).
|
|
211
|
+
|
|
212
|
+
Default implementation returns empty dict.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
client: NotionClient instance
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary mapping manager names to manager instances
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
>>> def register_sdk_managers(self, client) -> dict[str, Any]:
|
|
222
|
+
... return {
|
|
223
|
+
... "organizations": OrganizationManager(client),
|
|
224
|
+
... }
|
|
225
|
+
"""
|
|
226
|
+
return {}
|
|
227
|
+
|
|
228
|
+
def sdk_initialize(self, client: "NotionClient") -> None:
|
|
229
|
+
"""
|
|
230
|
+
Initialize SDK plugin resources.
|
|
231
|
+
|
|
232
|
+
Called after all registrations are complete. Use this for
|
|
233
|
+
setup tasks like loading configuration, validating resources,
|
|
234
|
+
or establishing connections.
|
|
235
|
+
|
|
236
|
+
Default implementation does nothing.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
client: NotionClient instance
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
>>> def sdk_initialize(self, client) -> None:
|
|
243
|
+
... # Load workspace configuration
|
|
244
|
+
... config = load_workspace_config()
|
|
245
|
+
... client._workspace_config = config
|
|
246
|
+
"""
|
|
247
|
+
pass
|
|
248
|
+
|
|
151
249
|
def validate(self) -> tuple[bool, list[str]]:
|
|
152
250
|
"""
|
|
153
251
|
Validate the plugin structure and configuration.
|
better_notion/plugins/loader.py
CHANGED
|
@@ -13,10 +13,13 @@ import importlib.util
|
|
|
13
13
|
import json
|
|
14
14
|
import sys
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
17
|
|
|
18
18
|
from better_notion.plugins.base import CommandPlugin
|
|
19
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from better_notion._sdk.client import NotionClient
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
class PluginLoader:
|
|
22
25
|
"""
|
|
@@ -242,3 +245,50 @@ class PluginLoader:
|
|
|
242
245
|
return True
|
|
243
246
|
|
|
244
247
|
return False
|
|
248
|
+
|
|
249
|
+
def register_sdk_extensions(self, client: "NotionClient") -> None:
|
|
250
|
+
"""
|
|
251
|
+
Register SDK extensions from all loaded plugins with a NotionClient.
|
|
252
|
+
|
|
253
|
+
This method iterates through all loaded plugins and registers their
|
|
254
|
+
SDK models, caches, and managers with the provided NotionClient instance.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
client: NotionClient instance to register extensions with
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
>>> loader = PluginLoader()
|
|
261
|
+
>>> loader.discover() # Load plugins first
|
|
262
|
+
>>> client = NotionClient(auth="...")
|
|
263
|
+
>>> loader.register_sdk_extensions(client)
|
|
264
|
+
"""
|
|
265
|
+
# Get all loaded plugins
|
|
266
|
+
plugins = list(self.loaded_plugins.values())
|
|
267
|
+
|
|
268
|
+
# Also discover plugins if not already loaded
|
|
269
|
+
if not plugins:
|
|
270
|
+
plugins = self.discover()
|
|
271
|
+
|
|
272
|
+
# Register SDK extensions from each plugin
|
|
273
|
+
for plugin in plugins:
|
|
274
|
+
try:
|
|
275
|
+
# Register models
|
|
276
|
+
models = plugin.register_sdk_models()
|
|
277
|
+
if models:
|
|
278
|
+
client._plugin_models.update(models)
|
|
279
|
+
|
|
280
|
+
# Register caches
|
|
281
|
+
caches = plugin.register_sdk_caches(client)
|
|
282
|
+
if caches:
|
|
283
|
+
client._plugin_caches.update(caches)
|
|
284
|
+
|
|
285
|
+
# Register managers
|
|
286
|
+
managers = plugin.register_sdk_managers(client)
|
|
287
|
+
if managers:
|
|
288
|
+
client._plugin_managers.update(managers)
|
|
289
|
+
|
|
290
|
+
# Initialize plugin
|
|
291
|
+
plugin.sdk_initialize(client)
|
|
292
|
+
except Exception:
|
|
293
|
+
# Continue with other plugins if one fails
|
|
294
|
+
continue
|
|
@@ -23,7 +23,9 @@ import typer
|
|
|
23
23
|
|
|
24
24
|
from better_notion._cli.config import Config
|
|
25
25
|
from better_notion._cli.response import format_error, format_success
|
|
26
|
+
from better_notion._sdk.cache import Cache
|
|
26
27
|
from better_notion._sdk.client import NotionClient
|
|
28
|
+
from better_notion._sdk.plugins import CombinedPluginInterface
|
|
27
29
|
from better_notion.plugins.base import PluginInterface
|
|
28
30
|
from better_notion.utils.agents import (
|
|
29
31
|
DependencyResolver,
|
|
@@ -40,17 +42,26 @@ def get_client() -> NotionClient:
|
|
|
40
42
|
return NotionClient(auth=config.token, timeout=config.timeout)
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
class AgentsPlugin(
|
|
45
|
+
class AgentsPlugin(CombinedPluginInterface):
|
|
44
46
|
"""
|
|
45
47
|
Official agents workflow management plugin.
|
|
46
48
|
|
|
47
49
|
This plugin provides tools for managing software development workflows
|
|
48
50
|
through Notion databases, enabling AI agents to coordinate work on projects.
|
|
49
51
|
|
|
50
|
-
Commands:
|
|
51
|
-
- init: Initialize a new workspace with all databases
|
|
52
|
-
- init-project: Initialize a new project with .notion file
|
|
53
|
-
- role: Manage project role
|
|
52
|
+
CLI Commands:
|
|
53
|
+
- agents init: Initialize a new workspace with all databases
|
|
54
|
+
- agents init-project: Initialize a new project with .notion file
|
|
55
|
+
- agents role: Manage project role
|
|
56
|
+
- orgs: Organizations CRUD commands
|
|
57
|
+
- projects: Projects CRUD commands
|
|
58
|
+
- versions: Versions CRUD commands
|
|
59
|
+
- tasks: Tasks CRUD and workflow commands
|
|
60
|
+
|
|
61
|
+
SDK Extensions:
|
|
62
|
+
- Organization, Project, Version, Task models
|
|
63
|
+
- Dedicated caches for each entity type
|
|
64
|
+
- Managers for convenient operations
|
|
54
65
|
"""
|
|
55
66
|
|
|
56
67
|
def register_commands(self, app: typer.Typer) -> None:
|
|
@@ -335,6 +346,140 @@ class AgentsPlugin(PluginInterface):
|
|
|
335
346
|
# Register agents app to main CLI
|
|
336
347
|
app.add_typer(agents_app)
|
|
337
348
|
|
|
349
|
+
# Import and register CRUD commands
|
|
350
|
+
from better_notion.plugins.official import agents_cli
|
|
351
|
+
|
|
352
|
+
# Organizations commands
|
|
353
|
+
orgs_app = typer.Typer(name="orgs", help="Organizations management commands")
|
|
354
|
+
orgs_app.command("list")(agents_cli.orgs_list)
|
|
355
|
+
orgs_app.command("get")(agents_cli.orgs_get)
|
|
356
|
+
orgs_app.command("create")(agents_cli.orgs_create)
|
|
357
|
+
app.add_typer(orgs_app)
|
|
358
|
+
|
|
359
|
+
# Projects commands
|
|
360
|
+
projects_app = typer.Typer(name="projects", help="Projects management commands")
|
|
361
|
+
projects_app.command("list")(projects_list_with_cli := lambda **kwargs: agents_cli.projects_list(**kwargs))
|
|
362
|
+
projects_app.command("get")(projects_get_with_cli := lambda project_id: agents_cli.projects_get(project_id))
|
|
363
|
+
projects_app.command("create")(lambda **kwargs: agents_cli.projects_create(**kwargs))
|
|
364
|
+
app.add_typer(projects_app)
|
|
365
|
+
|
|
366
|
+
# Versions commands
|
|
367
|
+
versions_app = typer.Typer(name="versions", help="Versions management commands")
|
|
368
|
+
versions_app.command("list")(lambda **kwargs: agents_cli.versions_list(**kwargs))
|
|
369
|
+
versions_app.command("get")(lambda version_id: agents_cli.versions_get(version_id))
|
|
370
|
+
versions_app.command("create")(lambda **kwargs: agents_cli.versions_create(**kwargs))
|
|
371
|
+
app.add_typer(versions_app)
|
|
372
|
+
|
|
373
|
+
# Tasks commands
|
|
374
|
+
tasks_app = typer.Typer(name="tasks", help="Tasks management commands")
|
|
375
|
+
tasks_app.command("list")(lambda **kwargs: agents_cli.tasks_list(**kwargs))
|
|
376
|
+
tasks_app.command("get")(lambda task_id: agents_cli.tasks_get(task_id))
|
|
377
|
+
tasks_app.command("create")(lambda **kwargs: agents_cli.tasks_create(**kwargs))
|
|
378
|
+
tasks_app.command("next")(lambda **kwargs: agents_cli.tasks_next(**kwargs))
|
|
379
|
+
tasks_app.command("claim")(lambda task_id: agents_cli.tasks_claim(task_id))
|
|
380
|
+
tasks_app.command("start")(lambda task_id: agents_cli.tasks_start(task_id))
|
|
381
|
+
tasks_app.command("complete")(lambda **kwargs: agents_cli.tasks_complete(**kwargs))
|
|
382
|
+
tasks_app.command("can-start")(lambda task_id: agents_cli.tasks_can_start(task_id))
|
|
383
|
+
app.add_typer(tasks_app)
|
|
384
|
+
|
|
385
|
+
# Ideas commands
|
|
386
|
+
ideas_app = typer.Typer(name="ideas", help="Ideas management commands")
|
|
387
|
+
ideas_app.command("list")(lambda **kwargs: agents_cli.ideas_list(**kwargs))
|
|
388
|
+
ideas_app.command("get")(lambda idea_id: agents_cli.ideas_get(idea_id))
|
|
389
|
+
ideas_app.command("create")(lambda **kwargs: agents_cli.ideas_create(**kwargs))
|
|
390
|
+
ideas_app.command("review")(lambda count: agents_cli.ideas_review(count))
|
|
391
|
+
ideas_app.command("accept")(lambda idea_id: agents_cli.ideas_accept(idea_id))
|
|
392
|
+
ideas_app.command("reject")(lambda idea_id, reason: agents_cli.ideas_reject(idea_id, reason))
|
|
393
|
+
app.add_typer(ideas_app)
|
|
394
|
+
|
|
395
|
+
# Work Issues commands
|
|
396
|
+
work_issues_app = typer.Typer(name="work-issues", help="Work Issues management commands")
|
|
397
|
+
work_issues_app.command("list")(lambda **kwargs: agents_cli.work_issues_list(**kwargs))
|
|
398
|
+
work_issues_app.command("get")(lambda issue_id: agents_cli.work_issues_get(issue_id))
|
|
399
|
+
work_issues_app.command("create")(lambda **kwargs: agents_cli.work_issues_create(**kwargs))
|
|
400
|
+
work_issues_app.command("resolve")(lambda issue_id, resolution: agents_cli.work_issues_resolve(issue_id, resolution))
|
|
401
|
+
work_issues_app.command("blockers")(lambda project_id: agents_cli.work_issues_blockers(project_id))
|
|
402
|
+
app.add_typer(work_issues_app)
|
|
403
|
+
|
|
404
|
+
# Incidents commands
|
|
405
|
+
incidents_app = typer.Typer(name="incidents", help="Incidents management commands")
|
|
406
|
+
incidents_app.command("list")(lambda **kwargs: agents_cli.incidents_list(**kwargs))
|
|
407
|
+
incidents_app.command("get")(lambda incident_id: agents_cli.incidents_get(incident_id))
|
|
408
|
+
incidents_app.command("create")(lambda **kwargs: agents_cli.incidents_create(**kwargs))
|
|
409
|
+
incidents_app.command("resolve")(lambda incident_id, resolution: agents_cli.incidents_resolve(incident_id, resolution))
|
|
410
|
+
incidents_app.command("mttr")(lambda **kwargs: agents_cli.incidents_mttr(**kwargs))
|
|
411
|
+
incidents_app.command("sla-violations")(lambda: agents_cli.incidents_sla_violations())
|
|
412
|
+
app.add_typer(incidents_app)
|
|
413
|
+
|
|
414
|
+
def register_sdk_models(self) -> dict[str, type]:
|
|
415
|
+
"""Register workflow entity models."""
|
|
416
|
+
from better_notion.plugins.official.agents_sdk.models import (
|
|
417
|
+
Idea,
|
|
418
|
+
Incident,
|
|
419
|
+
Organization,
|
|
420
|
+
Project,
|
|
421
|
+
Task,
|
|
422
|
+
Version,
|
|
423
|
+
WorkIssue,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
"Organization": Organization,
|
|
428
|
+
"Project": Project,
|
|
429
|
+
"Version": Version,
|
|
430
|
+
"Task": Task,
|
|
431
|
+
"Idea": Idea,
|
|
432
|
+
"WorkIssue": WorkIssue,
|
|
433
|
+
"Incident": Incident,
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
def register_sdk_caches(self, client: NotionClient) -> dict[str, Cache]:
|
|
437
|
+
"""Register dedicated caches for workflow entities."""
|
|
438
|
+
return {
|
|
439
|
+
"organizations": Cache(),
|
|
440
|
+
"projects": Cache(),
|
|
441
|
+
"versions": Cache(),
|
|
442
|
+
"tasks": Cache(),
|
|
443
|
+
"ideas": Cache(),
|
|
444
|
+
"work_issues": Cache(),
|
|
445
|
+
"incidents": Cache(),
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
def register_sdk_managers(self, client: NotionClient) -> dict:
|
|
449
|
+
"""Register custom managers for workflow entities."""
|
|
450
|
+
from better_notion.plugins.official.agents_sdk.managers import (
|
|
451
|
+
IdeaManager,
|
|
452
|
+
IncidentManager,
|
|
453
|
+
OrganizationManager,
|
|
454
|
+
ProjectManager,
|
|
455
|
+
TaskManager,
|
|
456
|
+
VersionManager,
|
|
457
|
+
WorkIssueManager,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
"organizations": OrganizationManager(client),
|
|
462
|
+
"projects": ProjectManager(client),
|
|
463
|
+
"versions": VersionManager(client),
|
|
464
|
+
"tasks": TaskManager(client),
|
|
465
|
+
"ideas": IdeaManager(client),
|
|
466
|
+
"work_issues": WorkIssueManager(client),
|
|
467
|
+
"incidents": IncidentManager(client),
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
def sdk_initialize(self, client: NotionClient) -> None:
|
|
471
|
+
"""Initialize plugin resources."""
|
|
472
|
+
import json
|
|
473
|
+
from pathlib import Path
|
|
474
|
+
|
|
475
|
+
config_path = Path.home() / ".notion" / "workspace.json"
|
|
476
|
+
|
|
477
|
+
if config_path.exists():
|
|
478
|
+
with open(config_path) as f:
|
|
479
|
+
client._workspace_config = json.load(f)
|
|
480
|
+
else:
|
|
481
|
+
client._workspace_config = {}
|
|
482
|
+
|
|
338
483
|
def get_info(self) -> dict[str, str | bool | list]:
|
|
339
484
|
"""Return plugin metadata."""
|
|
340
485
|
return {
|