better-notion 0.9.4__tar.gz → 0.9.6__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.
Files changed (161) hide show
  1. {better_notion-0.9.4 → better_notion-0.9.6}/PKG-INFO +1 -1
  2. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/plugins.py +127 -34
  3. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/update.py +51 -30
  4. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/main.py +14 -3
  5. better_notion-0.9.6/better_notion/plugins/state.py +123 -0
  6. {better_notion-0.9.4 → better_notion-0.9.6}/pyproject.toml +1 -1
  7. better_notion-0.9.6/tests/plugins/test_plugin_commands_state.py +294 -0
  8. better_notion-0.9.6/tests/plugins/test_state.py +212 -0
  9. {better_notion-0.9.4 → better_notion-0.9.6}/.gitignore +0 -0
  10. {better_notion-0.9.4 → better_notion-0.9.6}/LICENSE +0 -0
  11. {better_notion-0.9.4 → better_notion-0.9.6}/README.md +0 -0
  12. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/__init__.py +0 -0
  13. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/__init__.py +0 -0
  14. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/client.py +0 -0
  15. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/__init__.py +0 -0
  16. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/blocks.py +0 -0
  17. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/comments.py +0 -0
  18. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/databases.py +0 -0
  19. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/pages.py +0 -0
  20. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/collections/users.py +0 -0
  21. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/__init__.py +0 -0
  22. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/block.py +0 -0
  23. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/comment.py +0 -0
  24. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/database.py +0 -0
  25. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/page.py +0 -0
  26. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/entities/user.py +0 -0
  27. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/errors.py +0 -0
  28. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/oauth.py +0 -0
  29. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/__init__.py +0 -0
  30. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/base.py +0 -0
  31. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/checkbox.py +0 -0
  32. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/date.py +0 -0
  33. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/email.py +0 -0
  34. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/number.py +0 -0
  35. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/phone.py +0 -0
  36. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/rich_text.py +0 -0
  37. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/select.py +0 -0
  38. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/title.py +0 -0
  39. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/properties/url.py +0 -0
  40. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/utils/__init__.py +0 -0
  41. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_api/utils/pagination.py +0 -0
  42. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/__init__.py +0 -0
  43. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/async_typer.py +0 -0
  44. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/__init__.py +0 -0
  45. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/auth.py +0 -0
  46. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/blocks.py +0 -0
  47. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/comments.py +0 -0
  48. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/config.py +0 -0
  49. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/databases.py +0 -0
  50. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/pages.py +0 -0
  51. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/search.py +0 -0
  52. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/users.py +0 -0
  53. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/commands/workspace.py +0 -0
  54. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/config.py +0 -0
  55. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/errors.py +0 -0
  56. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/markdown.py +0 -0
  57. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/response.py +0 -0
  58. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_cli/utils/__init__.py +0 -0
  59. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/__init__.py +0 -0
  60. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/base/__init__.py +0 -0
  61. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/base/entity.py +0 -0
  62. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/cache/__init__.py +0 -0
  63. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/cache/cache.py +0 -0
  64. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/client.py +0 -0
  65. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/__init__.py +0 -0
  66. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/block_manager.py +0 -0
  67. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/comment_manager.py +0 -0
  68. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/database_manager.py +0 -0
  69. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/page_manager.py +0 -0
  70. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/managers/user_manager.py +0 -0
  71. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/__init__.py +0 -0
  72. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/block.py +0 -0
  73. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/__init__.py +0 -0
  74. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/audio.py +0 -0
  75. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/bookmark.py +0 -0
  76. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/breadcrumb.py +0 -0
  77. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/bullet.py +0 -0
  78. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/callout.py +0 -0
  79. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/code.py +0 -0
  80. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/column.py +0 -0
  81. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/column_list.py +0 -0
  82. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/divider.py +0 -0
  83. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/embed.py +0 -0
  84. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/equation.py +0 -0
  85. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/file.py +0 -0
  86. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/heading.py +0 -0
  87. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/image.py +0 -0
  88. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/numbered.py +0 -0
  89. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/paragraph.py +0 -0
  90. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/pdf.py +0 -0
  91. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/quote.py +0 -0
  92. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/synced_block.py +0 -0
  93. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/table.py +0 -0
  94. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/table_row.py +0 -0
  95. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/template.py +0 -0
  96. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/todo.py +0 -0
  97. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/toggle.py +0 -0
  98. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/blocks/video.py +0 -0
  99. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/comment.py +0 -0
  100. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/database.py +0 -0
  101. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/page.py +0 -0
  102. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/models/user.py +0 -0
  103. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/parents/__init__.py +0 -0
  104. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/properties/__init__.py +0 -0
  105. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/properties/formula.py +0 -0
  106. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/properties/parsers.py +0 -0
  107. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/properties/relation.py +0 -0
  108. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/query/__init__.py +0 -0
  109. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/query/database_query.py +0 -0
  110. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/_sdk/query/filter_translator.py +0 -0
  111. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/plugins/__init__.py +0 -0
  112. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/plugins/base.py +0 -0
  113. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/plugins/loader.py +0 -0
  114. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/plugins/official/__init__.py +0 -0
  115. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/plugins/official/productivity.py +0 -0
  116. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/utils/__init__.py +0 -0
  117. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/utils/helpers.py +0 -0
  118. {better_notion-0.9.4 → better_notion-0.9.6}/better_notion/utils/retry.py +0 -0
  119. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/base/test_entity.py +0 -0
  120. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/cache/test_cache.py +0 -0
  121. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/__init__.py +0 -0
  122. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/blocks/test_advanced_blocks.py +0 -0
  123. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/test_block.py +0 -0
  124. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/test_database.py +0 -0
  125. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/test_database_bug.py +0 -0
  126. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/test_page.py +0 -0
  127. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/models/test_user.py +0 -0
  128. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/properties/test_formula.py +0 -0
  129. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/properties/test_parsers.py +0 -0
  130. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/properties/test_relation.py +0 -0
  131. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/query/test_database_query.py +0 -0
  132. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/query/test_filter_translator.py +0 -0
  133. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/test_client.py +0 -0
  134. {better_notion-0.9.4 → better_notion-0.9.6}/tests/_sdk/test_comment.py +0 -0
  135. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/__init__.py +0 -0
  136. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_async_typer.py +0 -0
  137. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_config.py +0 -0
  138. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_errors.py +0 -0
  139. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_main.py +0 -0
  140. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_pages_commands.py +0 -0
  141. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_response.py +0 -0
  142. {better_notion-0.9.4 → better_notion-0.9.6}/tests/cli/test_update.py +0 -0
  143. {better_notion-0.9.4 → better_notion-0.9.6}/tests/conftest.py +0 -0
  144. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/conftest.py +0 -0
  145. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/test_blocks.py +0 -0
  146. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/test_databases.py +0 -0
  147. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/test_pages.py +0 -0
  148. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/test_search.py +0 -0
  149. {better_notion-0.9.4 → better_notion-0.9.6}/tests/integration/test_users.py +0 -0
  150. {better_notion-0.9.4 → better_notion-0.9.6}/tests/plugins/__init__.py +0 -0
  151. {better_notion-0.9.4 → better_notion-0.9.6}/tests/plugins/test_base.py +0 -0
  152. {better_notion-0.9.4 → better_notion-0.9.6}/tests/plugins/test_loader.py +0 -0
  153. {better_notion-0.9.4 → better_notion-0.9.6}/tests/plugins/test_marketplace.py +0 -0
  154. {better_notion-0.9.4 → better_notion-0.9.6}/tests/plugins/test_productivity_plugin.py +0 -0
  155. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_client.py +0 -0
  156. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_collections.py +0 -0
  157. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_entities.py +0 -0
  158. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_errors.py +0 -0
  159. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_helpers.py +0 -0
  160. {better_notion-0.9.4 → better_notion-0.9.6}/tests/unit/test_properties.py +0 -0
  161. {better_notion-0.9.4 → better_notion-0.9.6}/tests/utils/test_retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: better-notion
3
- Version: 0.9.4
3
+ Version: 0.9.6
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
@@ -183,40 +183,87 @@ def list_plugins(
183
183
  """
184
184
  List all installed and available plugins.
185
185
 
186
+ Shows both official plugins (built-in) and user-installed plugins.
187
+
186
188
  Examples:
187
189
  notion plugin list
188
190
  notion plugin list --verbose
189
191
  notion plugin list --json
190
192
  """
191
193
  try:
194
+ from better_notion.plugins.official import OFFICIAL_PLUGINS
195
+ from better_notion.plugins.state import PluginStateManager
196
+
192
197
  loader = get_plugin_loader()
193
- plugins = loader.list_plugins()
198
+ state_manager = PluginStateManager()
199
+
200
+ # Get user plugins
201
+ user_plugins = loader.list_plugins()
202
+
203
+ # Get official plugins with their state
204
+ official_plugins = {}
205
+ for plugin_class in OFFICIAL_PLUGINS:
206
+ try:
207
+ plugin = plugin_class()
208
+ info = plugin.get_info()
209
+ plugin_name = info.get('name')
210
+
211
+ # Add state information
212
+ info['bundled'] = True
213
+ info['enabled'] = state_manager.is_enabled(plugin_name)
214
+
215
+ official_plugins[plugin_name] = info
216
+ except Exception:
217
+ # Skip plugins that fail to instantiate
218
+ continue
219
+
220
+ # Merge both
221
+ all_plugins = {**official_plugins, **user_plugins}
194
222
 
195
223
  if json_output:
196
- return typer.echo(json.dumps(plugins, indent=2))
224
+ return typer.echo(json.dumps(all_plugins, indent=2))
197
225
 
198
- # Display as table
199
- if not plugins:
200
- typer.echo("No plugins installed.")
226
+ # Display as formatted output
227
+ if not all_plugins:
228
+ typer.echo("No plugins found.")
201
229
  typer.echo(f"\nPlugins directory: {Path.home() / '.notion' / 'plugins'}")
202
230
  return
203
231
 
204
- typer.echo("Installed Plugins:")
205
- typer.echo("" + "─" * 50 + "┐")
232
+ typer.echo("Plugins:")
233
+ typer.echo("=" * 70)
206
234
 
207
- for name, info in plugins.items():
208
- official_marker = "✓" if info.get("official") else " "
209
- typer.echo(f"│ {official_marker} {name:30} │ {info.get('description', 'No description'):40} │")
235
+ # Group by type
236
+ has_official = bool(official_plugins)
237
+ has_user = bool(user_plugins)
210
238
 
211
- if verbose:
212
- typer.echo(f"│ Version: {info.get('version', 'unknown'):20} │")
213
- typer.echo(f"│ Author: {info.get('author', 'unknown'):20} │")
214
- typer.echo("│" + "─" * 50 + "│")
239
+ if has_official:
240
+ typer.echo("\nOfficial Plugins (built-in):")
241
+ for name, info in official_plugins.items():
242
+ enabled = info.get('enabled', True)
243
+ status = "✓ Enabled" if enabled else "✗ Disabled"
244
+ typer.echo(f" • {name:20} {info.get('description', 'No description'):40} [{status}]")
245
+
246
+ if verbose:
247
+ typer.echo(f" Version: {info.get('version', 'unknown')}")
248
+ typer.echo(f" Author: {info.get('author', 'unknown')}")
249
+ if info.get('category'):
250
+ typer.echo(f" Category: {info.get('category')}")
251
+
252
+ if has_user:
253
+ if has_official:
254
+ typer.echo("\nUser Plugins:")
255
+ for name, info in user_plugins.items():
256
+ official_marker = "✓" if info.get("official") else " "
257
+ typer.echo(f" • [{official_marker}] {name:20} {info.get('description', 'No description'):40}")
258
+
259
+ if verbose:
260
+ typer.echo(f" Version: {info.get('version', 'unknown')}")
261
+ typer.echo(f" Author: {info.get('author', 'unknown')}")
215
262
 
216
- typer.echo("└" + "─" * 50 + "┘")
263
+ typer.echo("\nTip: Use 'notion plugin enable/disable <name>' to toggle official plugins")
217
264
 
218
265
  except Exception as e:
219
- return format_error("LIST_ERROR", str(e))
266
+ return format_error("LIST_ERROR", str(e))
220
267
 
221
268
 
222
269
  @app.command()
@@ -476,14 +523,33 @@ def enable(
476
523
  """
477
524
  Enable a plugin.
478
525
 
526
+ For official plugins, this re-enables them if they were disabled.
527
+ For user plugins, this adds them to the enabled list.
528
+
479
529
  Examples:
530
+ notion plugin enable productivity
480
531
  notion plugin enable organizations
481
532
  """
482
- # Create enabled plugins list
483
- config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
484
- config_file.parent.mkdir(parents=True, exist_ok=True)
485
-
486
533
  try:
534
+ loader = get_plugin_loader()
535
+
536
+ # Check if it's an official plugin
537
+ if loader.is_official_plugin(plugin_name):
538
+ from better_notion.plugins.state import PluginStateManager
539
+
540
+ state_manager = PluginStateManager()
541
+ state_manager.enable(plugin_name)
542
+
543
+ return format_success({
544
+ "plugin": plugin_name,
545
+ "status": "enabled",
546
+ "type": "official"
547
+ })
548
+
549
+ # For user plugins, use the existing enabled.json logic
550
+ config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
551
+ config_file.parent.mkdir(parents=True, exist_ok=True)
552
+
487
553
  if config_file.exists():
488
554
  config = json.loads(config_file.read_text())
489
555
  else:
@@ -495,11 +561,12 @@ def enable(
495
561
 
496
562
  return format_success({
497
563
  "plugin": plugin_name,
498
- "status": "enabled"
564
+ "status": "enabled",
565
+ "type": "user"
499
566
  })
500
567
 
501
568
  except Exception as e:
502
- return format_error("ENABLE_ERROR", str(e))
569
+ return format_error("ENABLE_ERROR", str(e))
503
570
 
504
571
 
505
572
  @app.command()
@@ -509,25 +576,51 @@ def disable(
509
576
  """
510
577
  Disable a plugin.
511
578
 
579
+ For official plugins, this disables them (but doesn't remove them).
580
+ For user plugins, this removes them from the enabled list.
581
+
512
582
  Examples:
583
+ notion plugin disable productivity
513
584
  notion plugin disable organizations
514
585
  """
515
- config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
516
-
517
586
  try:
518
- if config_file.exists():
519
- config = json.loads(config_file.read_text())
520
- if plugin_name in config.get("enabled", []):
521
- config["enabled"].remove(plugin_name)
522
- config_file.write_text(json.dumps(config, indent=2))
587
+ loader = get_plugin_loader()
523
588
 
524
- return format_success({
525
- "plugin": plugin_name,
526
- "status": "disabled"
527
- })
589
+ # Check if it's an official plugin
590
+ if loader.is_official_plugin(plugin_name):
591
+ from better_notion.plugins.state import PluginStateManager
592
+
593
+ state_manager = PluginStateManager()
594
+ state_manager.disable(plugin_name)
595
+
596
+ return format_success({
597
+ "plugin": plugin_name,
598
+ "status": "disabled",
599
+ "type": "official",
600
+ "message": "Plugin will be disabled after CLI restart"
601
+ })
602
+
603
+ # For user plugins, use the existing enabled.json logic
604
+ config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
605
+
606
+ try:
607
+ if config_file.exists():
608
+ config = json.loads(config_file.read_text())
609
+ if plugin_name in config.get("enabled", []):
610
+ config["enabled"].remove(plugin_name)
611
+ config_file.write_text(json.dumps(config, indent=2))
612
+
613
+ return format_success({
614
+ "plugin": plugin_name,
615
+ "status": "disabled",
616
+ "type": "user"
617
+ })
618
+
619
+ except Exception as e:
620
+ return format_error("DISABLE_ERROR", str(e))
528
621
 
529
622
  except Exception as e:
530
- return format_error("DISABLE_ERROR", str(e))
623
+ return format_error("DISABLE_ERROR", str(e))
531
624
 
532
625
 
533
626
  @app.command()
@@ -13,40 +13,19 @@ import typer
13
13
  from better_notion._cli.async_typer import AsyncTyper
14
14
  from better_notion._cli.response import format_error, format_success
15
15
 
16
- app = AsyncTyper(help="Update Better Notion CLI", invoke_without_command=True)
16
+ app = AsyncTyper(help="Update Better Notion CLI", invoke_without_command=True, no_args_is_help=False)
17
17
 
18
18
 
19
- @app.callback()
20
- def main(
21
- ctx: typer.Context,
22
- check: bool = typer.Option(False, "--check", "-c", help="Only check if an update is available"),
23
- ) -> None:
19
+ def _perform_upgrade(check: bool = False) -> None:
24
20
  """
25
- Update Better Notion CLI to the latest version.
21
+ Internal function to perform the upgrade.
26
22
 
27
- If no subcommand is specified, performs an update by default.
28
- """
29
- # If no subcommand was provided, execute upgrade
30
- if ctx.invoked_subcommand is None:
31
- upgrade(check=check)
32
-
33
-
34
- @app.command()
35
- def upgrade(
36
- check: bool = typer.Option(False, "--check", "-c", help="Only check if an update is available"),
37
- ) -> None:
23
+ Args:
24
+ check: If True, only check for updates without installing
38
25
  """
39
- Update Better Notion CLI to the latest version.
40
-
41
- This is a simple wrapper around pip that checks for and installs updates.
42
- pip handles all version checking, downloading, and dependency management.
26
+ import platform
27
+ import sys
43
28
 
44
- Examples:
45
- notion update # Install latest version (default)
46
- notion update upgrade # Same as above
47
- notion update --check # Check for updates only
48
- notion update upgrade --check # Same as above
49
- """
50
29
  package_name = "better-notion"
51
30
 
52
31
  if check:
@@ -85,6 +64,14 @@ def upgrade(
85
64
  else:
86
65
  # Perform the actual upgrade
87
66
  typer.echo(f"Updating {package_name} to the latest version...")
67
+
68
+ # Warn Windows users about potential file locking
69
+ if platform.system() == "Windows":
70
+ typer.echo("")
71
+ typer.echo("⚠ Note: On Windows, if you see a file access error,")
72
+ typer.echo(" close all terminals and run: pip install --upgrade better-notion")
73
+ typer.echo("")
74
+
88
75
  typer.echo("")
89
76
 
90
77
  try:
@@ -112,6 +99,40 @@ def upgrade(
112
99
  return format_error("UPDATE_ERROR", str(e))
113
100
 
114
101
 
102
+ @app.callback()
103
+ def main(
104
+ ctx: typer.Context,
105
+ check: bool = typer.Option(False, "--check", "-c", help="Only check if an update is available"),
106
+ ) -> None:
107
+ """
108
+ Update Better Notion CLI to the latest version.
109
+
110
+ If no subcommand is specified, performs an update by default.
111
+ """
112
+ # If no subcommand was provided, execute upgrade
113
+ if ctx.invoked_subcommand is None:
114
+ _perform_upgrade(check=check)
115
+
116
+
117
+ @app.command()
118
+ def upgrade(
119
+ check: bool = typer.Option(False, "--check", "-c", help="Only check if an update is available"),
120
+ ) -> None:
121
+ """
122
+ Update Better Notion CLI to the latest version.
123
+
124
+ This is a simple wrapper around pip that checks for and installs updates.
125
+ pip handles all version checking, downloading, and dependency management.
126
+
127
+ Examples:
128
+ notion update # Install latest version (default)
129
+ notion update upgrade # Same as above
130
+ notion update --check # Check for updates only
131
+ notion update upgrade --check # Same as above
132
+ """
133
+ _perform_upgrade(check=check)
134
+
135
+
115
136
  @app.command()
116
137
  def check() -> None:
117
138
  """
@@ -122,7 +143,7 @@ def check() -> None:
122
143
  Examples:
123
144
  notion update check
124
145
  """
125
- upgrade(check=True)
146
+ _perform_upgrade(check=True)
126
147
 
127
148
 
128
149
  @app.command()
@@ -135,4 +156,4 @@ def self() -> None:
135
156
  Examples:
136
157
  notion update self
137
158
  """
138
- upgrade(check=False)
159
+ _perform_upgrade(check=False)
@@ -41,22 +41,33 @@ app.add_typer(update.app, name="update")
41
41
 
42
42
  # Load and register official plugins
43
43
  def _load_official_plugins():
44
- """Load and register official plugins."""
44
+ """Load and register official plugins, respecting their enabled/disabled state."""
45
45
  try:
46
46
  from better_notion.plugins.official import OFFICIAL_PLUGINS
47
47
  from better_notion.plugins.loader import PluginLoader
48
+ from better_notion.plugins.state import PluginStateManager
48
49
 
49
50
  loader = PluginLoader()
51
+ state_manager = PluginStateManager()
50
52
 
51
53
  for plugin_class in OFFICIAL_PLUGINS:
52
54
  try:
53
55
  plugin = plugin_class()
54
- plugin.register_commands(app)
55
56
  info = plugin.get_info()
57
+ plugin_name = info.get('name')
58
+
59
+ # Check if plugin is disabled
60
+ if not state_manager.is_enabled(plugin_name):
61
+ # Skip loading this plugin
62
+ continue
63
+
64
+ # Register the plugin's commands
65
+ plugin.register_commands(app)
66
+
56
67
  # Store plugin for later reference
57
68
  if not hasattr(app, '_loaded_plugins'):
58
69
  app._loaded_plugins = {}
59
- app._loaded_plugins[info['name']] = plugin
70
+ app._loaded_plugins[plugin_name] = plugin
60
71
  except Exception as e:
61
72
  # Log but don't fail if a plugin fails to load
62
73
  pass
@@ -0,0 +1,123 @@
1
+ """
2
+ Plugin state management for Better Notion CLI.
3
+
4
+ This module handles the state of official plugins (enabled/disabled).
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ class PluginStateManager:
15
+ """Manages the enabled/disabled state of official plugins."""
16
+
17
+ def __init__(self, state_file: Path | None = None):
18
+ """
19
+ Initialize the plugin state manager.
20
+
21
+ Args:
22
+ state_file: Path to the state file. Defaults to ~/.notion/plugins/state.json
23
+ """
24
+ self.state_file = state_file or (Path.home() / ".notion" / "plugins" / "state.json")
25
+ self.state_file.parent.mkdir(parents=True, exist_ok=True)
26
+ self._state = self._load_state()
27
+
28
+ def _load_state(self) -> dict[str, Any]:
29
+ """Load state from file."""
30
+ if self.state_file.exists():
31
+ try:
32
+ return json.loads(self.state_file.read_text())
33
+ except (json.JSONDecodeError, IOError):
34
+ pass
35
+ return {"official_plugins": {}}
36
+
37
+ def _save_state(self) -> None:
38
+ """Save state to file."""
39
+ self.state_file.write_text(json.dumps(self._state, indent=2))
40
+
41
+ def is_enabled(self, plugin_name: str) -> bool:
42
+ """
43
+ Check if a plugin is enabled.
44
+
45
+ Args:
46
+ plugin_name: Name of the plugin
47
+
48
+ Returns:
49
+ True if the plugin is enabled, False if disabled
50
+ """
51
+ plugin_state = self._state.get("official_plugins", {}).get(plugin_name, {})
52
+ return plugin_state.get("state") != "disabled"
53
+
54
+ def enable(self, plugin_name: str) -> None:
55
+ """
56
+ Enable a plugin.
57
+
58
+ Args:
59
+ plugin_name: Name of the plugin to enable
60
+ """
61
+ if "official_plugins" not in self._state:
62
+ self._state["official_plugins"] = {}
63
+
64
+ # Remove disabled state if it exists
65
+ if plugin_name in self._state["official_plugins"]:
66
+ del self._state["official_plugins"][plugin_name]
67
+
68
+ self._save_state()
69
+
70
+ def disable(self, plugin_name: str) -> None:
71
+ """
72
+ Disable a plugin.
73
+
74
+ Args:
75
+ plugin_name: Name of the plugin to disable
76
+ """
77
+ if "official_plugins" not in self._state:
78
+ self._state["official_plugins"] = {}
79
+
80
+ self._state["official_plugins"][plugin_name] = {
81
+ "state": "disabled",
82
+ "disabled_at": datetime.now().isoformat()
83
+ }
84
+
85
+ self._save_state()
86
+
87
+ def get_plugin_state(self, plugin_name: str) -> dict[str, Any] | None:
88
+ """
89
+ Get the state of a specific plugin.
90
+
91
+ Args:
92
+ plugin_name: Name of the plugin
93
+
94
+ Returns:
95
+ Plugin state dict or None if no state exists
96
+ """
97
+ return self._state.get("official_plugins", {}).get(plugin_name)
98
+
99
+ def get_all_states(self) -> dict[str, dict[str, Any]]:
100
+ """
101
+ Get states of all plugins.
102
+
103
+ Returns:
104
+ Dict mapping plugin names to their states
105
+ """
106
+ return self._state.get("official_plugins", {})
107
+
108
+ def is_official_plugin(self, plugin_name: str) -> bool:
109
+ """
110
+ Check if a plugin is an official plugin.
111
+
112
+ This is a helper that can be used to determine if a plugin
113
+ should be managed through the state system.
114
+
115
+ Args:
116
+ plugin_name: Name of the plugin
117
+
118
+ Returns:
119
+ True if the plugin is official
120
+ """
121
+ # This will be checked against the OFFICIAL_PLUGINS list
122
+ # For now, return False - the actual check is done elsewhere
123
+ return False
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "better-notion"
3
- version = "0.9.4"
3
+ version = "0.9.6"
4
4
  description = "A high-level Python SDK for the Notion API with developer experience in mind."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"