better-notion 1.5.4__py3-none-any.whl → 1.6.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/_cli/docs/__init__.py +20 -0
- better_notion/_cli/docs/base.py +204 -0
- better_notion/_cli/docs/formatters.py +128 -0
- better_notion/_cli/docs/registry.py +82 -0
- better_notion/_cli/main.py +167 -0
- better_notion/_cli/response.py +64 -8
- better_notion/plugins/official/agents.py +274 -3
- better_notion/plugins/official/agents_schema.py +369 -0
- better_notion/utils/agents/metadata.py +185 -0
- better_notion/utils/agents/workspace.py +49 -2
- {better_notion-1.5.4.dist-info → better_notion-1.6.0.dist-info}/METADATA +1 -1
- {better_notion-1.5.4.dist-info → better_notion-1.6.0.dist-info}/RECORD +15 -9
- {better_notion-1.5.4.dist-info → better_notion-1.6.0.dist-info}/WHEEL +0 -0
- {better_notion-1.5.4.dist-info → better_notion-1.6.0.dist-info}/entry_points.txt +0 -0
- {better_notion-1.5.4.dist-info → better_notion-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Agents plugin schema for AI agent documentation.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive documentation about the agents workflow
|
|
4
|
+
system that AI agents can consume to understand how to work with the system.
|
|
5
|
+
|
|
6
|
+
This is the SINGLE SOURCE OF TRUTH for agents documentation. All other
|
|
7
|
+
documentation (CLI help, website, etc.) should be derived from this schema.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from better_notion._cli.docs.base import (
|
|
11
|
+
Command,
|
|
12
|
+
Concept,
|
|
13
|
+
Schema,
|
|
14
|
+
Workflow,
|
|
15
|
+
WorkflowStep,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# CONCEPTS
|
|
20
|
+
# =============================================================================
|
|
21
|
+
|
|
22
|
+
WORKSPACE_CONCEPT = Concept(
|
|
23
|
+
name="workspace",
|
|
24
|
+
description=(
|
|
25
|
+
"A workspace is a collection of 8 interconnected databases that implement "
|
|
26
|
+
"a complete software development workflow management system. It provides "
|
|
27
|
+
"the structure for tracking organizations, projects, versions, tasks, ideas, "
|
|
28
|
+
"work issues, and incidents in a unified manner."
|
|
29
|
+
),
|
|
30
|
+
properties={
|
|
31
|
+
"databases": [
|
|
32
|
+
"Organizations",
|
|
33
|
+
"Tags",
|
|
34
|
+
"Projects",
|
|
35
|
+
"Versions",
|
|
36
|
+
"Tasks",
|
|
37
|
+
"Ideas",
|
|
38
|
+
"Work Issues",
|
|
39
|
+
"Incidents",
|
|
40
|
+
],
|
|
41
|
+
"initialization": "Created via 'agents init' command",
|
|
42
|
+
"detection": "Automatically detected by scanning for expected databases",
|
|
43
|
+
"uniqueness": "One workspace per Notion page",
|
|
44
|
+
},
|
|
45
|
+
relationships={
|
|
46
|
+
"Organizations → Projects": "Many-to-one (many projects belong to one organization)",
|
|
47
|
+
"Projects → Versions": "Many-to-one (many versions belong to one project)",
|
|
48
|
+
"Versions → Tasks": "Many-to-one (many tasks belong to one version)",
|
|
49
|
+
"Tasks → Tasks": "Self-referential (tasks can depend on other tasks)",
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
TASK_CONCEPT = Concept(
|
|
54
|
+
name="task",
|
|
55
|
+
description=(
|
|
56
|
+
"A task represents a unit of work that needs to be completed as part of "
|
|
57
|
+
"a project version. Tasks have states (Todo, In Progress, Done, Cancelled) "
|
|
58
|
+
"and can depend on other tasks."
|
|
59
|
+
),
|
|
60
|
+
properties={
|
|
61
|
+
"required_properties": {
|
|
62
|
+
"Title": "Task name (title property)",
|
|
63
|
+
"Status": "Current state: Todo, In Progress, Done, Cancelled",
|
|
64
|
+
"Version": "Relation to Version database (required)",
|
|
65
|
+
},
|
|
66
|
+
"optional_properties": {
|
|
67
|
+
"Target Version": "Version where task should be implemented",
|
|
68
|
+
"Dependencies": "Other tasks this task depends on",
|
|
69
|
+
"Dependent Tasks": "Tasks that depend on this task",
|
|
70
|
+
},
|
|
71
|
+
"workflow": "Todo → In Progress → Done",
|
|
72
|
+
},
|
|
73
|
+
relationships={
|
|
74
|
+
"Version": "Required - each task must belong to one version",
|
|
75
|
+
"Dependencies": "Optional - tasks that must complete before this task",
|
|
76
|
+
"Dependent Tasks": "Inverse of dependencies - tasks blocked by this task",
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
PROJECT_CONCEPT = Concept(
|
|
81
|
+
name="project",
|
|
82
|
+
description=(
|
|
83
|
+
"A project represents a software project or product being developed. "
|
|
84
|
+
"Projects belong to organizations and contain multiple versions."
|
|
85
|
+
),
|
|
86
|
+
properties={
|
|
87
|
+
"required_properties": {
|
|
88
|
+
"Title": "Project name",
|
|
89
|
+
"Organization": "Relation to organization (required)",
|
|
90
|
+
},
|
|
91
|
+
"contains": ["Versions", "Tasks", "Ideas", "Work Issues", "Incidents"],
|
|
92
|
+
},
|
|
93
|
+
relationships={
|
|
94
|
+
"Organization": "Required - each project belongs to one organization",
|
|
95
|
+
"Versions": "One-to-many - project contains multiple versions",
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
VERSION_CONCEPT = Concept(
|
|
100
|
+
name="version",
|
|
101
|
+
description=(
|
|
102
|
+
"A version represents a release or milestone of a project. Examples: "
|
|
103
|
+
"v1.0.0, v1.1.0, v2.0.0. Tasks are created within versions."
|
|
104
|
+
),
|
|
105
|
+
properties={
|
|
106
|
+
"required_properties": {
|
|
107
|
+
"Title": "Version name (e.g., v1.0.0)",
|
|
108
|
+
"Project": "Relation to project (required)",
|
|
109
|
+
},
|
|
110
|
+
"contains": ["Tasks"],
|
|
111
|
+
"examples": ["v1.0.0", "v1.1.0", "v2.0.0-beta", "sprint-1"],
|
|
112
|
+
},
|
|
113
|
+
relationships={
|
|
114
|
+
"Project": "Required - each version belongs to one project",
|
|
115
|
+
"Tasks": "One-to-many - version contains multiple tasks",
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# =============================================================================
|
|
120
|
+
# WORKFLOWS
|
|
121
|
+
# =============================================================================
|
|
122
|
+
|
|
123
|
+
INITIALIZE_WORKSPACE = Workflow(
|
|
124
|
+
name="initialize_workspace",
|
|
125
|
+
description="Create a complete agents workflow management system with 8 databases",
|
|
126
|
+
steps=[
|
|
127
|
+
WorkflowStep(
|
|
128
|
+
description="Detect existing workspace in page",
|
|
129
|
+
purpose="Prevent duplicate workspace creation",
|
|
130
|
+
),
|
|
131
|
+
WorkflowStep(
|
|
132
|
+
description="Create Organizations database",
|
|
133
|
+
command="notion databases create --parent PAGE_ID --title Organizations",
|
|
134
|
+
),
|
|
135
|
+
WorkflowStep(
|
|
136
|
+
description="Create Tags database",
|
|
137
|
+
command="notion databases create --parent PAGE_ID --title Tags",
|
|
138
|
+
),
|
|
139
|
+
WorkflowStep(
|
|
140
|
+
description="Create Projects database",
|
|
141
|
+
command="notion databases create --parent PAGE_ID --title Projects",
|
|
142
|
+
),
|
|
143
|
+
WorkflowStep(
|
|
144
|
+
description="Create Versions database",
|
|
145
|
+
command="notion databases create --parent PAGE_ID --title Versions",
|
|
146
|
+
),
|
|
147
|
+
WorkflowStep(
|
|
148
|
+
description="Create Tasks database",
|
|
149
|
+
command="notion databases create --parent PAGE_ID --title Tasks",
|
|
150
|
+
),
|
|
151
|
+
WorkflowStep(
|
|
152
|
+
description="Create Ideas database",
|
|
153
|
+
command="notion databases create --parent PAGE_ID --title Ideas",
|
|
154
|
+
),
|
|
155
|
+
WorkflowStep(
|
|
156
|
+
description="Create Work Issues database",
|
|
157
|
+
command="notion databases create --parent PAGE_ID --title 'Work Issues'",
|
|
158
|
+
),
|
|
159
|
+
WorkflowStep(
|
|
160
|
+
description="Create Incidents database",
|
|
161
|
+
command="notion databases create --parent PAGE_ID --title Incidents",
|
|
162
|
+
),
|
|
163
|
+
WorkflowStep(
|
|
164
|
+
description="Establish database relationships",
|
|
165
|
+
purpose="Create relations between databases (Projects→Organizations, etc.)",
|
|
166
|
+
),
|
|
167
|
+
WorkflowStep(
|
|
168
|
+
description="Save workspace metadata",
|
|
169
|
+
command="agents info --parent-page PAGE_ID",
|
|
170
|
+
purpose="Verify setup and get database IDs",
|
|
171
|
+
),
|
|
172
|
+
],
|
|
173
|
+
commands=[
|
|
174
|
+
"agents init --parent-page PAGE_ID",
|
|
175
|
+
"agents info --parent-page PAGE_ID",
|
|
176
|
+
],
|
|
177
|
+
prerequisites=["valid_page_id"],
|
|
178
|
+
error_recovery={
|
|
179
|
+
"workspace_exists": {
|
|
180
|
+
"message": "Detected 5+ expected databases in page",
|
|
181
|
+
"meaning": "Workspace already initialized in this page",
|
|
182
|
+
"solutions": [
|
|
183
|
+
{
|
|
184
|
+
"flag": "--skip",
|
|
185
|
+
"action": "use_existing_workspace",
|
|
186
|
+
"description": "Skip initialization and use existing workspace",
|
|
187
|
+
"when_to_use": "You want to keep existing data",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"flag": "--reset",
|
|
191
|
+
"action": "recreate_workspace",
|
|
192
|
+
"description": "Delete all databases and recreate (WARNING: data loss)",
|
|
193
|
+
"warning": "This will delete all existing databases and their content",
|
|
194
|
+
"when_to_use": "You want to start completely fresh",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
CREATE_TASK_WORKFLOW = Workflow(
|
|
202
|
+
name="create_task",
|
|
203
|
+
description="Create a new task in the agents workflow system",
|
|
204
|
+
steps=[
|
|
205
|
+
WorkflowStep(
|
|
206
|
+
description="Verify workspace is initialized",
|
|
207
|
+
command="agents info --parent-page PAGE_ID",
|
|
208
|
+
purpose="Get database IDs and verify workspace exists",
|
|
209
|
+
),
|
|
210
|
+
WorkflowStep(
|
|
211
|
+
description="Identify target Version",
|
|
212
|
+
purpose="Tasks must belong to a Version (required relation)",
|
|
213
|
+
),
|
|
214
|
+
WorkflowStep(
|
|
215
|
+
description="Create task page with proper properties",
|
|
216
|
+
command="notion pages create --parent TASKS_DB_ID --title 'Task Name' --properties '{...}'",
|
|
217
|
+
purpose="Create task with Status and Version relation",
|
|
218
|
+
),
|
|
219
|
+
],
|
|
220
|
+
commands=[
|
|
221
|
+
"agents info --parent-page PAGE_ID",
|
|
222
|
+
"notion pages create --parent TASKS_DB_ID --title 'Task' --properties '{\"Status\": \"Todo\", \"Version\": \"VERSION_ID\"}'",
|
|
223
|
+
],
|
|
224
|
+
prerequisites=["workspace_initialized"],
|
|
225
|
+
error_recovery={
|
|
226
|
+
"workspace_not_found": {
|
|
227
|
+
"message": "No workspace detected in page",
|
|
228
|
+
"solution": "Run 'agents init' first to create workspace",
|
|
229
|
+
},
|
|
230
|
+
"missing_version_relation": {
|
|
231
|
+
"message": "Task must have a Version relation",
|
|
232
|
+
"solution": "Always specify Version property when creating task",
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
QUERY_TASKS_WORKFLOW = Workflow(
|
|
238
|
+
name="query_tasks",
|
|
239
|
+
description="Query and filter tasks in the workspace",
|
|
240
|
+
steps=[
|
|
241
|
+
WorkflowStep(
|
|
242
|
+
description="Get workspace database IDs",
|
|
243
|
+
command="agents info --parent-page PAGE_ID",
|
|
244
|
+
purpose="Obtain tasks database ID",
|
|
245
|
+
),
|
|
246
|
+
WorkflowStep(
|
|
247
|
+
description="Query tasks database",
|
|
248
|
+
command="notion databases query --database TASKS_DB_ID --filter '{...}'",
|
|
249
|
+
purpose="Retrieve tasks with optional filtering",
|
|
250
|
+
),
|
|
251
|
+
],
|
|
252
|
+
commands=[
|
|
253
|
+
"agents info --parent-page PAGE_ID",
|
|
254
|
+
"notion databases query --database TASKS_DB_ID",
|
|
255
|
+
],
|
|
256
|
+
prerequisites=["workspace_initialized"],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# COMMANDS
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
INIT_COMMAND = Command(
|
|
264
|
+
name="init",
|
|
265
|
+
purpose="Initialize a new agents workspace or manage existing one",
|
|
266
|
+
description=(
|
|
267
|
+
"Creates a complete workflow management system with 8 databases "
|
|
268
|
+
"and their relationships. Can detect existing workspaces to prevent duplicates."
|
|
269
|
+
),
|
|
270
|
+
flags={
|
|
271
|
+
"--parent-page": "Parent page ID where workspace will be created",
|
|
272
|
+
"--workspace-name": "Name for the workspace (default: 'Agents Workspace')",
|
|
273
|
+
"--reset": "Force recreation (deletes existing databases and recreates)",
|
|
274
|
+
"--skip": "Skip initialization if workspace already exists",
|
|
275
|
+
"--debug": "Enable debug output",
|
|
276
|
+
},
|
|
277
|
+
workflow="initialize_workspace",
|
|
278
|
+
when_to_use=[
|
|
279
|
+
"First time setting up agents system in a page",
|
|
280
|
+
"Starting fresh in a new page",
|
|
281
|
+
"Recovering from corrupted workspace (use --reset)",
|
|
282
|
+
"Safely checking for existing workspace (use --skip)",
|
|
283
|
+
],
|
|
284
|
+
error_recovery={
|
|
285
|
+
"workspace_exists": {
|
|
286
|
+
"solutions": [
|
|
287
|
+
{"flag": "--skip", "use_case": "Keep existing workspace"},
|
|
288
|
+
{"flag": "--reset", "use_case": "Delete and recreate (data loss)"},
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
INFO_COMMAND = Command(
|
|
295
|
+
name="info",
|
|
296
|
+
purpose="Display workspace status and metadata",
|
|
297
|
+
description="Shows whether a workspace exists, database IDs, and workspace info",
|
|
298
|
+
flags={
|
|
299
|
+
"--parent-page": "Parent page ID to check for workspace",
|
|
300
|
+
},
|
|
301
|
+
workflow=None,
|
|
302
|
+
when_to_use=[
|
|
303
|
+
"Verify workspace initialization",
|
|
304
|
+
"Get database IDs for queries",
|
|
305
|
+
"Check workspace version and metadata",
|
|
306
|
+
"Debug workspace issues",
|
|
307
|
+
],
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# =============================================================================
|
|
311
|
+
# COMPLETE SCHEMA
|
|
312
|
+
# =============================================================================
|
|
313
|
+
|
|
314
|
+
AGENTS_SCHEMA = Schema(
|
|
315
|
+
name="agents",
|
|
316
|
+
version="1.0.0",
|
|
317
|
+
description=(
|
|
318
|
+
"Workflow management system for software development. "
|
|
319
|
+
"Provides complete structure for tracking organizations, projects, "
|
|
320
|
+
"versions, tasks, ideas, work issues, and incidents."
|
|
321
|
+
),
|
|
322
|
+
concepts=[
|
|
323
|
+
WORKSPACE_CONCEPT,
|
|
324
|
+
TASK_CONCEPT,
|
|
325
|
+
PROJECT_CONCEPT,
|
|
326
|
+
VERSION_CONCEPT,
|
|
327
|
+
],
|
|
328
|
+
workflows=[
|
|
329
|
+
INITIALIZE_WORKSPACE,
|
|
330
|
+
CREATE_TASK_WORKFLOW,
|
|
331
|
+
QUERY_TASKS_WORKFLOW,
|
|
332
|
+
],
|
|
333
|
+
commands={
|
|
334
|
+
"init": INIT_COMMAND,
|
|
335
|
+
"info": INFO_COMMAND,
|
|
336
|
+
},
|
|
337
|
+
best_practices=[
|
|
338
|
+
"Always run 'agents info' before database operations to verify workspace state",
|
|
339
|
+
"Use --skip flag to safely check for existing workspaces (prevents duplicates)",
|
|
340
|
+
"Use --reset flag only when you need to recreate workspace (causes data loss)",
|
|
341
|
+
"Tasks must have a Version relation - always specify Version property",
|
|
342
|
+
"Check task dependencies before marking tasks as complete",
|
|
343
|
+
"Projects belong to Organizations - create organization first",
|
|
344
|
+
"Versions belong to Projects - create project first",
|
|
345
|
+
"Query tasks by Status to find next available task",
|
|
346
|
+
],
|
|
347
|
+
examples={
|
|
348
|
+
"initial_setup": """# First time setup
|
|
349
|
+
notion agents init --parent-page PAGE_ID
|
|
350
|
+
|
|
351
|
+
# Verify setup
|
|
352
|
+
notion agents info --parent-page PAGE_ID""",
|
|
353
|
+
|
|
354
|
+
"safe_initialization": """# Check if workspace exists, use if found
|
|
355
|
+
notion agents init --parent-page PAGE_ID --skip""",
|
|
356
|
+
|
|
357
|
+
"force_recreate": """# Delete existing workspace and recreate (WARNING: data loss)
|
|
358
|
+
notion agents init --parent-page PAGE_ID --reset""",
|
|
359
|
+
|
|
360
|
+
"query_workspace": """# Get workspace info and database IDs
|
|
361
|
+
notion agents info --parent-page PAGE_ID""",
|
|
362
|
+
|
|
363
|
+
"create_task": """# After workspace is initialized
|
|
364
|
+
notion pages create \\
|
|
365
|
+
--parent TASKS_DB_ID \\
|
|
366
|
+
--title "Fix login bug" \\
|
|
367
|
+
--properties '{"Status": "Todo", "Version": "VERSION_ID"}'""",
|
|
368
|
+
},
|
|
369
|
+
)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Workspace metadata management for agents plugin.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to manage workspace metadata,
|
|
4
|
+
including detecting duplicate workspaces and storing workspace information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
from better_notion._sdk.client import NotionClient
|
|
15
|
+
from better_notion._sdk.models.page import Page
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkspaceMetadata:
|
|
21
|
+
"""Manage workspace metadata for duplicate detection."""
|
|
22
|
+
|
|
23
|
+
# Property names used in Notion page
|
|
24
|
+
PROP_WORKSPACE_ID = "agents_workspace_id"
|
|
25
|
+
PROP_WORKSPACE_NAME = "agents_workspace_name"
|
|
26
|
+
PROP_INITIALIZED_AT = "agents_workspace_created"
|
|
27
|
+
PROP_VERSION = "agents_workspace_version"
|
|
28
|
+
PROP_DATABASE_IDS = "agents_workspace_databases"
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def generate_workspace_id() -> str:
|
|
32
|
+
"""Generate a unique workspace ID.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Unique workspace ID (UUID without hyphens for compactness)
|
|
36
|
+
"""
|
|
37
|
+
return str(uuid4()).replace("-", "")
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def extract_metadata_from_page(page: Page) -> dict[str, Any]:
|
|
41
|
+
"""Extract workspace metadata from page properties.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
page: Page object to extract metadata from
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dict with workspace metadata (empty if not initialized)
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> metadata = WorkspaceMetadata.extract_metadata_from_page(page)
|
|
51
|
+
>>> if metadata.get("workspace_id"):
|
|
52
|
+
... print(f"Workspace: {metadata['workspace_name']}")
|
|
53
|
+
"""
|
|
54
|
+
metadata = {}
|
|
55
|
+
|
|
56
|
+
# Try to get workspace ID from icon (stored as emoji)
|
|
57
|
+
# Notion doesn't support custom properties, so we use creative approaches
|
|
58
|
+
|
|
59
|
+
# Check if page icon contains our metadata marker
|
|
60
|
+
icon = page.icon
|
|
61
|
+
if icon and icon.startswith("🤖"):
|
|
62
|
+
metadata["is_agents_workspace"] = True
|
|
63
|
+
# Could encode data in icon, but keep it simple for now
|
|
64
|
+
|
|
65
|
+
return metadata
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
async def detect_workspace(
|
|
69
|
+
page: Page,
|
|
70
|
+
client: NotionClient
|
|
71
|
+
) -> dict[str, Any] | None:
|
|
72
|
+
"""Detect if page already has an agents workspace.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
page: Parent page to check
|
|
76
|
+
client: NotionClient instance
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Workspace metadata dict if found, None otherwise
|
|
80
|
+
"""
|
|
81
|
+
# Primary detection: Scan for databases with expected names
|
|
82
|
+
expected_databases = [
|
|
83
|
+
"Organizations",
|
|
84
|
+
"Tags",
|
|
85
|
+
"Projects",
|
|
86
|
+
"Versions",
|
|
87
|
+
"Tasks",
|
|
88
|
+
"Ideas",
|
|
89
|
+
"Work Issues",
|
|
90
|
+
"Incidents"
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Search for databases in this page
|
|
95
|
+
results = await client.search(
|
|
96
|
+
query="",
|
|
97
|
+
filter={"value": "database", "property": "object"}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
databases_found = []
|
|
101
|
+
database_ids = {}
|
|
102
|
+
|
|
103
|
+
for result in results:
|
|
104
|
+
if hasattr(result, 'title') and result.title in expected_databases:
|
|
105
|
+
databases_found.append(result.title)
|
|
106
|
+
# Extract the key name (lowercase with underscores)
|
|
107
|
+
key = result.title.lower().replace(" ", "_")
|
|
108
|
+
database_ids[key] = result.id
|
|
109
|
+
|
|
110
|
+
# Check if we have at least 5 of the expected databases
|
|
111
|
+
matches = len(databases_found)
|
|
112
|
+
if matches >= 5:
|
|
113
|
+
logger.info(f"Detected existing workspace with {matches}/{len(expected_databases)} databases in page {page.id}")
|
|
114
|
+
|
|
115
|
+
# Try to load workspace metadata from local config to get workspace_id
|
|
116
|
+
workspace_id = None
|
|
117
|
+
workspace_name = None
|
|
118
|
+
initialized_at = None
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
config_path = Path.home() / ".notion" / "workspace.json"
|
|
122
|
+
if config_path.exists():
|
|
123
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
124
|
+
config = json.load(f)
|
|
125
|
+
# Only use config if it matches this page
|
|
126
|
+
if config.get("parent_page") == page.id:
|
|
127
|
+
workspace_id = config.get("workspace_id")
|
|
128
|
+
workspace_name = config.get("workspace_name")
|
|
129
|
+
initialized_at = config.get("initialized_at")
|
|
130
|
+
logger.info(f"Config file matches this page, workspace_id: {workspace_id}")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.debug(f"Could not load local config: {e}")
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"workspace_id": workspace_id,
|
|
136
|
+
"workspace_name": workspace_name,
|
|
137
|
+
"initialized_at": initialized_at,
|
|
138
|
+
"database_ids": database_ids,
|
|
139
|
+
"detection_method": "database_scan",
|
|
140
|
+
"databases_count": matches
|
|
141
|
+
}
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.debug(f"Could not scan for databases: {e}")
|
|
144
|
+
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def save_workspace_config(
|
|
149
|
+
page_id: str,
|
|
150
|
+
workspace_id: str,
|
|
151
|
+
workspace_name: str,
|
|
152
|
+
database_ids: dict[str, str],
|
|
153
|
+
path: Optional[Path] = None
|
|
154
|
+
) -> Path:
|
|
155
|
+
"""Save workspace configuration to local file.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
page_id: Parent page ID
|
|
159
|
+
workspace_id: Unique workspace ID
|
|
160
|
+
workspace_name: Workspace name
|
|
161
|
+
database_ids: Dict of database name to ID
|
|
162
|
+
path: Optional custom path for config file
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Path to saved config file
|
|
166
|
+
"""
|
|
167
|
+
if path is None:
|
|
168
|
+
path = Path.home() / ".notion" / "workspace.json"
|
|
169
|
+
|
|
170
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
|
|
172
|
+
config = {
|
|
173
|
+
"workspace_id": workspace_id,
|
|
174
|
+
"workspace_name": workspace_name,
|
|
175
|
+
"parent_page": page_id,
|
|
176
|
+
"initialized_at": datetime.now(timezone.utc).isoformat(),
|
|
177
|
+
"version": "1.5.4", # Track version for migrations
|
|
178
|
+
"database_ids": database_ids
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
182
|
+
json.dump(config, f, indent=2)
|
|
183
|
+
|
|
184
|
+
logger.info(f"Saved workspace config to {path}")
|
|
185
|
+
return path
|
|
@@ -6,12 +6,14 @@ required databases for the workflow management system.
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
|
+
from datetime import datetime, timezone
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, Dict, Optional
|
|
11
12
|
|
|
12
13
|
from better_notion._cli.config import Config
|
|
13
14
|
from better_notion._sdk.client import NotionClient
|
|
14
15
|
from better_notion._sdk.models.page import Page
|
|
16
|
+
from better_notion.utils.agents.metadata import WorkspaceMetadata
|
|
15
17
|
from better_notion.utils.agents.schemas import (
|
|
16
18
|
IncidentSchema,
|
|
17
19
|
IdeaSchema,
|
|
@@ -48,11 +50,15 @@ class WorkspaceInitializer:
|
|
|
48
50
|
"""
|
|
49
51
|
self._client = client
|
|
50
52
|
self._database_ids: Dict[str, str] = {}
|
|
53
|
+
self._workspace_id: Optional[str] = None
|
|
54
|
+
self._parent_page_id: Optional[str] = None
|
|
55
|
+
self._workspace_name: Optional[str] = None
|
|
51
56
|
|
|
52
57
|
async def initialize_workspace(
|
|
53
58
|
self,
|
|
54
59
|
parent_page_id: str,
|
|
55
60
|
workspace_name: str = "Agents Workspace",
|
|
61
|
+
skip_detection: bool = False,
|
|
56
62
|
) -> Dict[str, str]:
|
|
57
63
|
"""Initialize a complete workspace with all databases.
|
|
58
64
|
|
|
@@ -62,15 +68,22 @@ class WorkspaceInitializer:
|
|
|
62
68
|
Args:
|
|
63
69
|
parent_page_id: ID of the parent page where databases will be created
|
|
64
70
|
workspace_name: Name for the workspace (used for database titles)
|
|
71
|
+
skip_detection: If True, skip duplicate detection (for reset operation)
|
|
65
72
|
|
|
66
73
|
Returns:
|
|
67
74
|
Dict mapping database names to their IDs
|
|
68
75
|
|
|
69
76
|
Raises:
|
|
70
77
|
Exception: If database creation fails with detailed error message
|
|
78
|
+
Exception: If workspace already exists (and skip_detection is False)
|
|
71
79
|
"""
|
|
72
80
|
logger.info(f"Initializing workspace '{workspace_name}' in page {parent_page_id}")
|
|
73
81
|
|
|
82
|
+
# Store workspace info
|
|
83
|
+
self._parent_page_id = parent_page_id
|
|
84
|
+
self._workspace_name = workspace_name
|
|
85
|
+
self._workspace_id = WorkspaceMetadata.generate_workspace_id()
|
|
86
|
+
|
|
74
87
|
# Get parent page
|
|
75
88
|
try:
|
|
76
89
|
parent = await Page.get(parent_page_id, client=self._client)
|
|
@@ -80,6 +93,26 @@ class WorkspaceInitializer:
|
|
|
80
93
|
logger.error(error_msg)
|
|
81
94
|
raise Exception(error_msg) from e
|
|
82
95
|
|
|
96
|
+
# Check for existing workspace unless skipped
|
|
97
|
+
if not skip_detection:
|
|
98
|
+
existing = await WorkspaceMetadata.detect_workspace(parent, self._client)
|
|
99
|
+
if existing:
|
|
100
|
+
workspace_info = {
|
|
101
|
+
"workspace_id": existing.get("workspace_id", "unknown"),
|
|
102
|
+
"workspace_name": existing.get("workspace_name", workspace_name),
|
|
103
|
+
"initialized_at": existing.get("initialized_at", "unknown"),
|
|
104
|
+
"databases": existing.get("database_ids", {})
|
|
105
|
+
}
|
|
106
|
+
error_msg = (
|
|
107
|
+
f"Workspace already initialized in this page\n"
|
|
108
|
+
f"Workspace ID: {workspace_info['workspace_id']}\n"
|
|
109
|
+
f"Initialized: {workspace_info['initialized_at']}\n"
|
|
110
|
+
f"Databases: {len(workspace_info.get('databases', {}))} created\n"
|
|
111
|
+
f"Use --reset to reinitialize or --skip to keep existing"
|
|
112
|
+
)
|
|
113
|
+
logger.error(error_msg)
|
|
114
|
+
raise Exception(error_msg) from None
|
|
115
|
+
|
|
83
116
|
# Create databases in order (independent first, then dependent)
|
|
84
117
|
self._database_ids = {}
|
|
85
118
|
databases_order = [
|
|
@@ -108,7 +141,11 @@ class WorkspaceInitializer:
|
|
|
108
141
|
logger.error(error_msg)
|
|
109
142
|
raise Exception(error_msg) from e
|
|
110
143
|
|
|
144
|
+
# Save workspace metadata
|
|
145
|
+
self.save_database_ids()
|
|
146
|
+
|
|
111
147
|
logger.info(f"Workspace initialization complete. Created {len(self._database_ids)} databases")
|
|
148
|
+
logger.info(f"Workspace ID: {self._workspace_id}")
|
|
112
149
|
return self._database_ids
|
|
113
150
|
|
|
114
151
|
async def _create_organizations_db(self, parent: Page) -> None:
|
|
@@ -312,7 +349,7 @@ class WorkspaceInitializer:
|
|
|
312
349
|
pass
|
|
313
350
|
|
|
314
351
|
def save_database_ids(self, path: Optional[Path] = None) -> None:
|
|
315
|
-
"""Save database IDs to
|
|
352
|
+
"""Save workspace metadata (database IDs and workspace info) to config file.
|
|
316
353
|
|
|
317
354
|
Args:
|
|
318
355
|
path: Path to save config file (default: ~/.notion/workspace.json)
|
|
@@ -322,8 +359,18 @@ class WorkspaceInitializer:
|
|
|
322
359
|
|
|
323
360
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
324
361
|
|
|
362
|
+
# Save full workspace metadata
|
|
363
|
+
config = {
|
|
364
|
+
"workspace_id": self._workspace_id,
|
|
365
|
+
"workspace_name": self._workspace_name,
|
|
366
|
+
"parent_page": self._parent_page_id,
|
|
367
|
+
"initialized_at": datetime.now(timezone.utc).isoformat(),
|
|
368
|
+
"version": "1.5.4",
|
|
369
|
+
"database_ids": self._database_ids
|
|
370
|
+
}
|
|
371
|
+
|
|
325
372
|
with open(path, "w", encoding="utf-8") as f:
|
|
326
|
-
json.dump(
|
|
373
|
+
json.dump(config, f, indent=2)
|
|
327
374
|
|
|
328
375
|
@classmethod
|
|
329
376
|
def load_database_ids(cls, path: Optional[Path] = None) -> Dict[str, str]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: better-notion
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: A high-level Python SDK for the Notion API with developer experience in mind.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nesalia-inc/better-notion
|
|
6
6
|
Project-URL: Documentation, https://github.com/nesalia-inc/better-notion#readme
|