better-notion 1.5.5__py3-none-any.whl → 1.7.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.
@@ -0,0 +1,20 @@
1
+ """Documentation system for AI agents.
2
+
3
+ This module provides infrastructure for documenting CLI commands
4
+ in a machine-readable format that AI agents can consume to understand
5
+ the system at a high level of abstraction.
6
+ """
7
+
8
+ from better_notion._cli.docs.base import (
9
+ Command,
10
+ Concept,
11
+ Schema,
12
+ Workflow,
13
+ )
14
+
15
+ __all__ = [
16
+ "Schema",
17
+ "Concept",
18
+ "Workflow",
19
+ "Command",
20
+ ]
@@ -0,0 +1,212 @@
1
+ """Base classes for documentation system.
2
+
3
+ This module provides the core data structures for documenting
4
+ CLI commands and workflows in a machine-readable format.
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class Concept:
13
+ """A concept represents a high-level idea in the system.
14
+
15
+ Concepts help AI agents understand what things are
16
+ (e.g., "workspace", "task", "version") beyond just
17
+ command syntax.
18
+
19
+ Attributes:
20
+ name: Name of the concept
21
+ description: Human-readable description
22
+ properties: Dict of concept properties and their meanings
23
+ relationships: Relationships to other concepts
24
+ """
25
+
26
+ name: str
27
+ description: str
28
+ properties: dict[str, Any] = field(default_factory=dict)
29
+ relationships: dict[str, str] = field(default_factory=dict)
30
+
31
+ def to_dict(self) -> dict[str, Any]:
32
+ """Convert to dictionary for JSON serialization."""
33
+ return {
34
+ "name": self.name,
35
+ "description": self.description,
36
+ "properties": self.properties,
37
+ "relationships": self.relationships,
38
+ }
39
+
40
+
41
+ @dataclass
42
+ class WorkflowStep:
43
+ """A single step in a workflow.
44
+
45
+ Attributes:
46
+ description: What this step does
47
+ command: Optional command to execute
48
+ purpose: Why this step is needed
49
+ """
50
+
51
+ description: str
52
+ command: str | None = None
53
+ purpose: str | None = None
54
+
55
+ def to_dict(self) -> dict[str, Any]:
56
+ """Convert to dictionary for JSON serialization."""
57
+ return {
58
+ "description": self.description,
59
+ "command": self.command,
60
+ "purpose": self.purpose,
61
+ }
62
+
63
+
64
+ @dataclass
65
+ class ErrorRecovery:
66
+ """Error recovery strategy for a command.
67
+
68
+ Attributes:
69
+ error_type: Type of error (e.g., "workspace_exists")
70
+ message: Human-readable error message
71
+ solutions: List of possible solutions with flags/actions
72
+ """
73
+
74
+ error_type: str
75
+ message: str
76
+ solutions: list[dict[str, Any]] = field(default_factory=list)
77
+
78
+ def to_dict(self) -> dict[str, Any]:
79
+ """Convert to dictionary for JSON serialization."""
80
+ return {
81
+ "error_type": self.error_type,
82
+ "message": self.message,
83
+ "solutions": self.solutions,
84
+ }
85
+
86
+
87
+ @dataclass
88
+ class Workflow:
89
+ """A workflow represents a sequence of operations.
90
+
91
+ Workflows help AI agents understand how to accomplish
92
+ high-level goals (e.g., "initialize_workspace",
93
+ "create_task").
94
+
95
+ Attributes:
96
+ name: Workflow name
97
+ description: What this workflow accomplishes
98
+ steps: List of steps in the workflow
99
+ commands: Example commands for this workflow
100
+ prerequisites: Required conditions before starting
101
+ error_recovery: Error handling strategies
102
+ """
103
+
104
+ name: str
105
+ description: str
106
+ steps: list[WorkflowStep | dict[str, Any]] = field(default_factory=list)
107
+ commands: list[str] = field(default_factory=list)
108
+ prerequisites: list[str] = field(default_factory=list)
109
+ error_recovery: dict[str, dict[str, Any]] = field(default_factory=dict)
110
+
111
+ def to_dict(self) -> dict[str, Any]:
112
+ """Convert to dictionary for JSON serialization."""
113
+ return {
114
+ "name": self.name,
115
+ "description": self.description,
116
+ "steps": [
117
+ step.to_dict() if isinstance(step, WorkflowStep) else step
118
+ for step in self.steps
119
+ ],
120
+ "commands": self.commands,
121
+ "prerequisites": self.prerequisites,
122
+ "error_recovery": self.error_recovery,
123
+ }
124
+
125
+
126
+ @dataclass
127
+ class Command:
128
+ """Documentation for a single command.
129
+
130
+ Command docs go beyond --help text to provide semantic
131
+ meaning and workflow context.
132
+
133
+ Attributes:
134
+ name: Command name
135
+ purpose: What this command does (semantic meaning)
136
+ description: Detailed description
137
+ flags: Dict of flag -> purpose
138
+ workflow: Which workflow this command belongs to
139
+ when_to_use: When this command should be used
140
+ error_recovery: Error handling strategies
141
+ subcommands: Dict of subcommand name -> subcommand documentation
142
+ """
143
+
144
+ name: str
145
+ purpose: str
146
+ description: str = ""
147
+ flags: dict[str, str] = field(default_factory=dict)
148
+ workflow: str | None = None
149
+ when_to_use: list[str] = field(default_factory=list)
150
+ error_recovery: dict[str, dict[str, Any]] = field(default_factory=dict)
151
+ subcommands: dict[str, dict[str, Any]] = field(default_factory=dict)
152
+
153
+ def to_dict(self) -> dict[str, Any]:
154
+ """Convert to dictionary for JSON serialization."""
155
+ result = {
156
+ "name": self.name,
157
+ "purpose": self.purpose,
158
+ "description": self.description,
159
+ "flags": self.flags,
160
+ "workflow": self.workflow,
161
+ "when_to_use": self.when_to_use,
162
+ "error_recovery": self.error_recovery,
163
+ }
164
+
165
+ # Add subcommands if present
166
+ if self.subcommands:
167
+ result["subcommands"] = self.subcommands
168
+
169
+ return result
170
+
171
+
172
+ @dataclass
173
+ class Schema:
174
+ """Complete schema documentation for a plugin.
175
+
176
+ A Schema provides everything an AI agent needs to understand
177
+ a plugin system at a high level.
178
+
179
+ Attributes:
180
+ name: Plugin name
181
+ version: Schema version
182
+ description: Plugin description
183
+ concepts: High-level concepts in the system
184
+ workflows: Available workflows
185
+ commands: Command documentation
186
+ best_practices: Recommended usage patterns
187
+ examples: Usage examples
188
+ """
189
+
190
+ name: str
191
+ version: str
192
+ description: str
193
+ concepts: list[Concept] = field(default_factory=list)
194
+ workflows: list[Workflow] = field(default_factory=list)
195
+ commands: dict[str, Command] = field(default_factory=dict)
196
+ best_practices: list[str] = field(default_factory=list)
197
+ examples: dict[str, str] = field(default_factory=dict)
198
+
199
+ def to_dict(self) -> dict[str, Any]:
200
+ """Convert to dictionary for JSON serialization."""
201
+ return {
202
+ "name": self.name,
203
+ "version": self.version,
204
+ "description": self.description,
205
+ "concepts": [concept.to_dict() for concept in self.concepts],
206
+ "workflows": [workflow.to_dict() for workflow in self.workflows],
207
+ "commands": {
208
+ name: command.to_dict() for name, command in self.commands.items()
209
+ },
210
+ "best_practices": self.best_practices,
211
+ "examples": self.examples,
212
+ }
@@ -0,0 +1,128 @@
1
+ """Formatters for schema documentation.
2
+
3
+ This module provides utilities to format Schema objects
4
+ into different output formats (JSON, YAML).
5
+ """
6
+
7
+ import json
8
+ from typing import Any
9
+
10
+ from better_notion._cli.docs.base import Schema
11
+
12
+
13
+ def format_schema_json(schema: Schema, *, pretty: bool = True) -> str:
14
+ """Format a Schema as JSON.
15
+
16
+ Args:
17
+ schema: Schema object to format
18
+ pretty: Whether to pretty-print with indentation
19
+
20
+ Returns:
21
+ JSON string representation of the schema
22
+ """
23
+ schema_dict = schema.to_dict()
24
+ if pretty:
25
+ return json.dumps(schema_dict, indent=2)
26
+ return json.dumps(schema_dict)
27
+
28
+
29
+ def format_schema_yaml(schema: Schema) -> str:
30
+ """Format a Schema as YAML.
31
+
32
+ Args:
33
+ schema: Schema object to format
34
+
35
+ Returns:
36
+ YAML string representation of the schema
37
+ """
38
+ try:
39
+ import yaml
40
+
41
+ return yaml.dump(schema.to_dict(), default_flow_style=False)
42
+ except ImportError:
43
+ # Fall back to JSON if pyyaml not installed
44
+ logger = __import__("logging").getLogger(__name__)
45
+ logger.warning("PyYAML not installed, falling back to JSON")
46
+ return format_schema_json(schema)
47
+
48
+
49
+ def format_schema_pretty(schema: Schema) -> str:
50
+ """Format a Schema as human-readable text with sections.
51
+
52
+ This provides a more readable format for human consumption
53
+ while maintaining machine-parsable structure.
54
+
55
+ Args:
56
+ schema: Schema object to format
57
+
58
+ Returns:
59
+ Formatted text string
60
+ """
61
+ lines = []
62
+ data = schema.to_dict()
63
+
64
+ # Header
65
+ lines.append(f"# {schema.name.upper()} SCHEMA")
66
+ lines.append(f"Version: {schema.version}")
67
+ lines.append(f"Description: {schema.description}")
68
+ lines.append("")
69
+
70
+ # Concepts
71
+ if data["concepts"]:
72
+ lines.append("## CONCEPTS")
73
+ for concept in data["concepts"]:
74
+ lines.append(f"### {concept['name']}")
75
+ lines.append(f"{concept['description']}")
76
+ if concept.get("properties"):
77
+ lines.append("**Properties:**")
78
+ for key, value in concept["properties"].items():
79
+ lines.append(f" - {key}: {value}")
80
+ if concept.get("relationships"):
81
+ lines.append("**Relationships:**")
82
+ for key, value in concept["relationships"].items():
83
+ lines.append(f" - {key}: {value}")
84
+ lines.append("")
85
+
86
+ # Workflows
87
+ if data["workflows"]:
88
+ lines.append("## WORKFLOWS")
89
+ for workflow in data["workflows"]:
90
+ lines.append(f"### {workflow['name']}")
91
+ lines.append(f"{workflow['description']}")
92
+ if workflow.get("steps"):
93
+ lines.append("**Steps:**")
94
+ for i, step in enumerate(workflow["steps"], 1):
95
+ if isinstance(step, dict):
96
+ lines.append(f" {i}. {step.get('description', step)}")
97
+ else:
98
+ lines.append(f" {i}. {step}")
99
+ if workflow.get("commands"):
100
+ lines.append("**Commands:**")
101
+ for cmd in workflow["commands"]:
102
+ lines.append(f" - {cmd}")
103
+ lines.append("")
104
+
105
+ # Commands
106
+ if data["commands"]:
107
+ lines.append("## COMMANDS")
108
+ for cmd_name, cmd in data["commands"].items():
109
+ lines.append(f"### {cmd_name}")
110
+ lines.append(f"**Purpose:** {cmd.get('purpose', 'N/A')}")
111
+ if cmd.get("flags"):
112
+ lines.append("**Flags:**")
113
+ for flag, purpose in cmd["flags"].items():
114
+ lines.append(f" - {flag}: {purpose}")
115
+ if cmd.get("when_to_use"):
116
+ lines.append("**When to use:**")
117
+ for usage in cmd["when_to_use"]:
118
+ lines.append(f" - {usage}")
119
+ lines.append("")
120
+
121
+ # Best Practices
122
+ if data.get("best_practices"):
123
+ lines.append("## BEST PRACTICES")
124
+ for practice in data["best_practices"]:
125
+ lines.append(f"- {practice}")
126
+ lines.append("")
127
+
128
+ return "\n".join(lines)
@@ -0,0 +1,82 @@
1
+ """Schema registry for plugin documentation.
2
+
3
+ This module provides a central registry where plugins can register
4
+ their documentation schemas for AI agent consumption.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ from better_notion._cli.docs.base import Schema
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SchemaRegistry:
16
+ """Registry for plugin documentation schemas.
17
+
18
+ This class maintains a collection of plugin schemas that
19
+ AI agents can query to understand the system.
20
+
21
+ Example:
22
+ >>> # Register a plugin schema
23
+ >>> SchemaRegistry.register("agents", AGENTS_SCHEMA)
24
+ >>>
25
+ >>> # Query for a plugin schema
26
+ >>> schema = SchemaRegistry.get("agents")
27
+ >>>
28
+ >>> # List all available schemas
29
+ >>> all_schemas = SchemaRegistry.list_all()
30
+ """
31
+
32
+ _schemas: dict[str, Schema] = {}
33
+
34
+ @classmethod
35
+ def register(cls, plugin_name: str, schema: Schema) -> None:
36
+ """Register a plugin schema.
37
+
38
+ Args:
39
+ plugin_name: Name of the plugin (e.g., "agents")
40
+ schema: Schema object containing plugin documentation
41
+ """
42
+ cls._schemas[plugin_name] = schema
43
+ logger.info(f"Registered schema for plugin: {plugin_name}")
44
+
45
+ @classmethod
46
+ def get(cls, plugin_name: str) -> Schema | None:
47
+ """Get a plugin schema by name.
48
+
49
+ Args:
50
+ plugin_name: Name of the plugin
51
+
52
+ Returns:
53
+ Schema object if found, None otherwise
54
+ """
55
+ return cls._schemas.get(plugin_name)
56
+
57
+ @classmethod
58
+ def list_all(cls) -> dict[str, Schema]:
59
+ """List all registered plugin schemas.
60
+
61
+ Returns:
62
+ Dict mapping plugin names to Schema objects
63
+ """
64
+ return cls._schemas.copy()
65
+
66
+ @classmethod
67
+ def get_all_names(cls) -> list[str]:
68
+ """Get list of all registered plugin names.
69
+
70
+ Returns:
71
+ List of plugin names
72
+ """
73
+ return list(cls._schemas.keys())
74
+
75
+ @classmethod
76
+ def clear(cls) -> None:
77
+ """Clear all registered schemas.
78
+
79
+ This is primarily useful for testing.
80
+ """
81
+ cls._schemas.clear()
82
+ logger.debug("Cleared schema registry")
@@ -118,6 +118,173 @@ def version(
118
118
  console.print(f"[bold blue]Better Notion CLI[/bold blue] version [bold green]{__version__}[/bold green]")
119
119
 
120
120
 
121
+ @app.command()
122
+ def docs(
123
+ topic: str = typer.Argument(None, help="Documentation topic to retrieve"),
124
+ format: str = typer.Option("json", "--format", "-f", help="Output format (json, yaml, pretty)"),
125
+ ) -> None:
126
+ """
127
+ Get system documentation for AI agents.
128
+
129
+ This command provides comprehensive documentation about the Better Notion CLI
130
+ that AI agents can consume to understand the system at a high level.
131
+
132
+ Without arguments: List all available topics
133
+ With topic: Show detailed documentation for that topic
134
+
135
+ Available topics:
136
+ - overview: System architecture and core concepts
137
+ - plugins: Available plugins and their capabilities
138
+ - commands: All available commands and their purposes
139
+
140
+ Examples:
141
+ $ notion docs # List all topics
142
+ $ notion docs overview # Get system overview
143
+ $ notion docs overview --format json
144
+ """
145
+ import json
146
+
147
+ # Define available topics
148
+ topics = {
149
+ "overview": {
150
+ "name": "overview",
151
+ "title": "System Overview",
152
+ "description": "Better Notion CLI - Command-line interface for Notion API",
153
+ "version": __version__,
154
+ "architecture": {
155
+ "description": "A CLI for interacting with Notion, designed for AI agents",
156
+ "features": [
157
+ "JSON-only output for programmatic parsing",
158
+ "Structured error codes for reliable error handling",
159
+ "Async command support for better performance",
160
+ "Plugin system for extensibility",
161
+ ]
162
+ },
163
+ "core_concepts": [
164
+ {
165
+ "name": "Pages",
166
+ "description": "Notion pages with content blocks",
167
+ "commands": ["notion pages get", "notion pages create", "notion pages update"]
168
+ },
169
+ {
170
+ "name": "Databases",
171
+ "description": "Notion databases with structured properties",
172
+ "commands": ["notion databases get", "notion databases query", "notion databases create"]
173
+ },
174
+ {
175
+ "name": "Blocks",
176
+ "description": "Content blocks (paragraphs, headings, lists, etc.)",
177
+ "commands": ["notion blocks get", "notion blocks create"]
178
+ },
179
+ {
180
+ "name": "Plugins",
181
+ "description": "Extensible command groups for specialized workflows",
182
+ "commands": ["notion agents", "notion plugin list"]
183
+ }
184
+ ],
185
+ "getting_started": [
186
+ "1. Configure authentication: notion auth login",
187
+ "2. Check status: notion auth status",
188
+ "3. Get a page: notion pages get <page_id>"
189
+ ]
190
+ },
191
+ "plugins": {
192
+ "name": "plugins",
193
+ "title": "Available Plugins",
194
+ "description": "Official plugins that extend CLI functionality",
195
+ "plugins": [
196
+ {
197
+ "name": "agents",
198
+ "description": "Workflow management system for software development",
199
+ "commands": ["agents init", "agents info", "agents schema"],
200
+ "documentation": "Run 'notion agents schema' for complete documentation"
201
+ }
202
+ ]
203
+ },
204
+ "commands": {
205
+ "name": "commands",
206
+ "title": "Available Commands",
207
+ "description": "Core CLI commands organized by category",
208
+ "categories": [
209
+ {
210
+ "name": "Authentication",
211
+ "commands": [
212
+ {"command": "notion auth login", "purpose": "Authenticate with Notion"},
213
+ {"command": "notion auth status", "purpose": "Check authentication status"},
214
+ {"command": "notion auth logout", "purpose": "Logout and clear credentials"}
215
+ ]
216
+ },
217
+ {
218
+ "name": "Pages",
219
+ "commands": [
220
+ {"command": "notion pages get <id>", "purpose": "Get page details"},
221
+ {"command": "notion pages create", "purpose": "Create a new page"},
222
+ {"command": "notion pages update <id>", "purpose": "Update page properties"},
223
+ {"command": "notion pages search <query>", "purpose": "Search for pages"}
224
+ ]
225
+ },
226
+ {
227
+ "name": "Databases",
228
+ "commands": [
229
+ {"command": "notion databases get <id>", "purpose": "Get database details"},
230
+ {"command": "notion databases query <id>", "purpose": "Query database with filters"},
231
+ {"command": "notion databases create", "purpose": "Create a new database"}
232
+ ]
233
+ },
234
+ {
235
+ "name": "Blocks",
236
+ "commands": [
237
+ {"command": "notion blocks get <id>", "purpose": "Get block details"},
238
+ {"command": "notion blocks children <id>", "purpose": "Get block children"}
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ }
244
+
245
+ # If no topic specified, list available topics
246
+ if topic is None:
247
+ topics_list = {
248
+ "available_topics": [
249
+ {
250
+ "name": name,
251
+ "title": info["title"],
252
+ "description": info["description"],
253
+ }
254
+ for name, info in topics.items()
255
+ ],
256
+ "usage": "notion docs <topic> for detailed information",
257
+ "examples": [
258
+ "notion docs overview",
259
+ "notion docs plugins",
260
+ "notion docs commands"
261
+ ]
262
+ }
263
+ typer.echo(json.dumps(topics_list, indent=2))
264
+ return
265
+
266
+ # Get the requested topic
267
+ topic_lower = topic.lower()
268
+ if topic_lower not in topics:
269
+ result = {
270
+ "success": False,
271
+ "error": {
272
+ "code": "UNKNOWN_TOPIC",
273
+ "message": f"Unknown topic: {topic}",
274
+ "retry": False,
275
+ "details": {
276
+ "available_topics": list(topics.keys())
277
+ }
278
+ }
279
+ }
280
+ typer.echo(json.dumps(result, indent=2))
281
+ raise typer.Exit(code=1)
282
+
283
+ # Return the topic documentation
284
+ topic_info = topics[topic_lower]
285
+ typer.echo(json.dumps(topic_info, indent=2))
286
+
287
+
121
288
  @app.callback()
122
289
  def main(
123
290
  ctx: typer.Context,