better-notion 0.9.6__tar.gz → 0.9.7__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 (165) hide show
  1. {better_notion-0.9.6 → better_notion-0.9.7}/PKG-INFO +2 -1
  2. better_notion-0.9.7/better_notion/_cli/commands/auth.py +136 -0
  3. better_notion-0.9.7/better_notion/_cli/commands/config.py +178 -0
  4. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/plugins.py +140 -92
  5. better_notion-0.9.7/better_notion/_cli/display.py +293 -0
  6. {better_notion-0.9.6 → better_notion-0.9.7}/pyproject.toml +4 -1
  7. better_notion-0.9.7/tests/cli/test_display.py +253 -0
  8. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_plugin_commands_state.py +30 -25
  9. better_notion-0.9.6/better_notion/_cli/commands/auth.py +0 -107
  10. better_notion-0.9.6/better_notion/_cli/commands/config.py +0 -131
  11. {better_notion-0.9.6 → better_notion-0.9.7}/.gitignore +0 -0
  12. {better_notion-0.9.6 → better_notion-0.9.7}/LICENSE +0 -0
  13. {better_notion-0.9.6 → better_notion-0.9.7}/README.md +0 -0
  14. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/__init__.py +0 -0
  15. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/__init__.py +0 -0
  16. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/client.py +0 -0
  17. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/__init__.py +0 -0
  18. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/blocks.py +0 -0
  19. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/comments.py +0 -0
  20. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/databases.py +0 -0
  21. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/pages.py +0 -0
  22. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/collections/users.py +0 -0
  23. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/__init__.py +0 -0
  24. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/block.py +0 -0
  25. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/comment.py +0 -0
  26. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/database.py +0 -0
  27. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/page.py +0 -0
  28. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/entities/user.py +0 -0
  29. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/errors.py +0 -0
  30. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/oauth.py +0 -0
  31. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/__init__.py +0 -0
  32. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/base.py +0 -0
  33. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/checkbox.py +0 -0
  34. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/date.py +0 -0
  35. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/email.py +0 -0
  36. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/number.py +0 -0
  37. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/phone.py +0 -0
  38. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/rich_text.py +0 -0
  39. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/select.py +0 -0
  40. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/title.py +0 -0
  41. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/properties/url.py +0 -0
  42. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/utils/__init__.py +0 -0
  43. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_api/utils/pagination.py +0 -0
  44. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/__init__.py +0 -0
  45. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/async_typer.py +0 -0
  46. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/__init__.py +0 -0
  47. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/blocks.py +0 -0
  48. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/comments.py +0 -0
  49. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/databases.py +0 -0
  50. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/pages.py +0 -0
  51. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/search.py +0 -0
  52. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/update.py +0 -0
  53. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/users.py +0 -0
  54. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/commands/workspace.py +0 -0
  55. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/config.py +0 -0
  56. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/errors.py +0 -0
  57. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/main.py +0 -0
  58. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/markdown.py +0 -0
  59. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/response.py +0 -0
  60. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_cli/utils/__init__.py +0 -0
  61. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/__init__.py +0 -0
  62. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/base/__init__.py +0 -0
  63. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/base/entity.py +0 -0
  64. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/cache/__init__.py +0 -0
  65. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/cache/cache.py +0 -0
  66. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/client.py +0 -0
  67. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/__init__.py +0 -0
  68. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/block_manager.py +0 -0
  69. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/comment_manager.py +0 -0
  70. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/database_manager.py +0 -0
  71. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/page_manager.py +0 -0
  72. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/managers/user_manager.py +0 -0
  73. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/__init__.py +0 -0
  74. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/block.py +0 -0
  75. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/__init__.py +0 -0
  76. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/audio.py +0 -0
  77. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/bookmark.py +0 -0
  78. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/breadcrumb.py +0 -0
  79. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/bullet.py +0 -0
  80. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/callout.py +0 -0
  81. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/code.py +0 -0
  82. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/column.py +0 -0
  83. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/column_list.py +0 -0
  84. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/divider.py +0 -0
  85. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/embed.py +0 -0
  86. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/equation.py +0 -0
  87. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/file.py +0 -0
  88. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/heading.py +0 -0
  89. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/image.py +0 -0
  90. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/numbered.py +0 -0
  91. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/paragraph.py +0 -0
  92. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/pdf.py +0 -0
  93. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/quote.py +0 -0
  94. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/synced_block.py +0 -0
  95. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/table.py +0 -0
  96. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/table_row.py +0 -0
  97. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/template.py +0 -0
  98. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/todo.py +0 -0
  99. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/toggle.py +0 -0
  100. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/blocks/video.py +0 -0
  101. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/comment.py +0 -0
  102. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/database.py +0 -0
  103. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/page.py +0 -0
  104. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/models/user.py +0 -0
  105. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/parents/__init__.py +0 -0
  106. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/properties/__init__.py +0 -0
  107. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/properties/formula.py +0 -0
  108. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/properties/parsers.py +0 -0
  109. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/properties/relation.py +0 -0
  110. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/query/__init__.py +0 -0
  111. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/query/database_query.py +0 -0
  112. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/_sdk/query/filter_translator.py +0 -0
  113. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/__init__.py +0 -0
  114. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/base.py +0 -0
  115. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/loader.py +0 -0
  116. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/official/__init__.py +0 -0
  117. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/official/productivity.py +0 -0
  118. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/plugins/state.py +0 -0
  119. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/utils/__init__.py +0 -0
  120. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/utils/helpers.py +0 -0
  121. {better_notion-0.9.6 → better_notion-0.9.7}/better_notion/utils/retry.py +0 -0
  122. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/base/test_entity.py +0 -0
  123. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/cache/test_cache.py +0 -0
  124. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/__init__.py +0 -0
  125. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/blocks/test_advanced_blocks.py +0 -0
  126. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/test_block.py +0 -0
  127. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/test_database.py +0 -0
  128. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/test_database_bug.py +0 -0
  129. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/test_page.py +0 -0
  130. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/models/test_user.py +0 -0
  131. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/properties/test_formula.py +0 -0
  132. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/properties/test_parsers.py +0 -0
  133. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/properties/test_relation.py +0 -0
  134. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/query/test_database_query.py +0 -0
  135. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/query/test_filter_translator.py +0 -0
  136. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/test_client.py +0 -0
  137. {better_notion-0.9.6 → better_notion-0.9.7}/tests/_sdk/test_comment.py +0 -0
  138. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/__init__.py +0 -0
  139. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_async_typer.py +0 -0
  140. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_config.py +0 -0
  141. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_errors.py +0 -0
  142. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_main.py +0 -0
  143. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_pages_commands.py +0 -0
  144. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_response.py +0 -0
  145. {better_notion-0.9.6 → better_notion-0.9.7}/tests/cli/test_update.py +0 -0
  146. {better_notion-0.9.6 → better_notion-0.9.7}/tests/conftest.py +0 -0
  147. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/conftest.py +0 -0
  148. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/test_blocks.py +0 -0
  149. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/test_databases.py +0 -0
  150. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/test_pages.py +0 -0
  151. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/test_search.py +0 -0
  152. {better_notion-0.9.6 → better_notion-0.9.7}/tests/integration/test_users.py +0 -0
  153. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/__init__.py +0 -0
  154. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_base.py +0 -0
  155. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_loader.py +0 -0
  156. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_marketplace.py +0 -0
  157. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_productivity_plugin.py +0 -0
  158. {better_notion-0.9.6 → better_notion-0.9.7}/tests/plugins/test_state.py +0 -0
  159. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_client.py +0 -0
  160. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_collections.py +0 -0
  161. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_entities.py +0 -0
  162. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_errors.py +0 -0
  163. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_helpers.py +0 -0
  164. {better_notion-0.9.6 → better_notion-0.9.7}/tests/unit/test_properties.py +0 -0
  165. {better_notion-0.9.6 → better_notion-0.9.7}/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.6
3
+ Version: 0.9.7
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
@@ -25,6 +25,7 @@ Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.10
26
26
  Requires-Dist: httpx<0.28.0,>=0.27.0
27
27
  Requires-Dist: pydantic<3.0.0,>=2.0.0
28
+ Requires-Dist: rich<14.0.0,>=13.0.0
28
29
  Requires-Dist: typing-extensions>=4.0.0
29
30
  Provides-Extra: all
30
31
  Requires-Dist: asyncer>=0.0.5; extra == 'all'
@@ -0,0 +1,136 @@
1
+ """
2
+ Authentication commands for Better Notion CLI.
3
+
4
+ This module provides commands for managing authentication tokens.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import typer
9
+
10
+ from better_notion._cli.async_typer import AsyncTyper
11
+ from better_notion._cli.config import Config
12
+ from better_notion._cli.display import is_human_mode, print_rich_error, print_rich_success
13
+
14
+ app = AsyncTyper(help="Authentication commands")
15
+
16
+
17
+ @app.command()
18
+ def login(
19
+ ctx: typer.Context,
20
+ token: str = typer.Option(
21
+ ...,
22
+ "--token",
23
+ "-t",
24
+ help="Notion API integration token",
25
+ prompt=True,
26
+ hide_input=True,
27
+ ),
28
+ json_output: bool = typer.Option(
29
+ False,
30
+ "--json",
31
+ "-j",
32
+ help="Output in JSON format (for AI agents)",
33
+ ),
34
+ ) -> None:
35
+ """
36
+ Authenticate with Notion API.
37
+
38
+ Stores the authentication token for subsequent CLI commands.
39
+ The token can be obtained from https://www.notion.so/my-integrations.
40
+ """
41
+ config_path = Config.get_config_path()
42
+
43
+ # Check if already logged in
44
+ if config_path.exists():
45
+ typer.confirm(
46
+ "You are already authenticated. Overwrite existing credentials?",
47
+ abort=True,
48
+ )
49
+
50
+ # Save the token
51
+ try:
52
+ Config.save(token=token)
53
+ print_rich_success(
54
+ "Successfully authenticated with Notion API",
55
+ data={"config_path": str(config_path)},
56
+ json_output=json_output and not is_human_mode()
57
+ )
58
+ except OSError as e:
59
+ print_rich_error(
60
+ f"Failed to save credentials: {e}",
61
+ details={"config_path": str(config_path)},
62
+ json_output=json_output and not is_human_mode()
63
+ )
64
+ raise typer.Exit(1)
65
+
66
+
67
+ @app.command()
68
+ def status(
69
+ json_output: bool = typer.Option(
70
+ False,
71
+ "--json",
72
+ "-j",
73
+ help="Output in JSON format (for AI agents)",
74
+ ),
75
+ ) -> None:
76
+ """
77
+ Check authentication status.
78
+
79
+ Verifies the stored authentication token and displays workspace information.
80
+ """
81
+ from better_notion._cli.display import print_rich_info
82
+
83
+ config = Config.load()
84
+
85
+ info = {
86
+ "status": "authenticated",
87
+ "token_preview": config.token[:20] + "...",
88
+ "timeout": config.timeout,
89
+ "retry_attempts": config.retry_attempts,
90
+ }
91
+
92
+ print_rich_info(
93
+ info,
94
+ title="Authentication Status",
95
+ json_output=json_output and not is_human_mode()
96
+ )
97
+
98
+
99
+ @app.command()
100
+ def logout(
101
+ json_output: bool = typer.Option(
102
+ False,
103
+ "--json",
104
+ "-j",
105
+ help="Output in JSON format (for AI agents)",
106
+ ),
107
+ ) -> None:
108
+ """
109
+ Remove stored credentials.
110
+
111
+ Deletes the authentication token from the configuration file.
112
+ """
113
+ from better_notion._cli.display import print_rich_error
114
+
115
+ config_path = Config.get_config_path()
116
+
117
+ if not config_path.exists():
118
+ print_rich_error(
119
+ "No credentials found",
120
+ details={"config_path": str(config_path)},
121
+ json_output=json_output and not is_human_mode()
122
+ )
123
+ raise typer.Exit(1)
124
+
125
+ try:
126
+ config_path.unlink()
127
+ print_rich_success(
128
+ f"Credentials removed from {config_path}",
129
+ json_output=json_output and not is_human_mode()
130
+ )
131
+ except OSError as e:
132
+ print_rich_error(
133
+ f"Failed to remove credentials: {e}",
134
+ json_output=json_output and not is_human_mode()
135
+ )
136
+ raise typer.Exit(1)
@@ -0,0 +1,178 @@
1
+ """
2
+ Config commands for Better Notion CLI.
3
+
4
+ This module provides commands for managing CLI configuration.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+
10
+ import typer
11
+
12
+ from better_notion._cli.async_typer import AsyncTyper
13
+ from better_notion._cli.config import Config
14
+ from better_notion._cli.display import is_human_mode, print_rich_error, print_rich_info, print_rich_success
15
+
16
+ app = AsyncTyper(help="Configuration commands")
17
+
18
+
19
+ @app.command("get")
20
+ def get(
21
+ key: str = typer.Argument(..., help="Configuration key to get"),
22
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
23
+ ) -> None:
24
+ """Get a configuration value."""
25
+ use_json = json_output or not is_human_mode()
26
+
27
+ try:
28
+ config = Config.load()
29
+
30
+ valid_keys = ["token", "default_database", "default_output", "timeout", "retry_attempts"]
31
+
32
+ if key not in valid_keys:
33
+ print_rich_error(
34
+ f"Invalid key. Valid keys: {', '.join(valid_keys)}",
35
+ code="INVALID_KEY",
36
+ json_output=use_json
37
+ )
38
+ raise typer.Exit(1)
39
+
40
+ value = getattr(config, key, None)
41
+
42
+ # Mask token for security
43
+ if key == "token" and value:
44
+ value = value[:20] + "..."
45
+
46
+ print_rich_info(
47
+ {"key": key, "value": value},
48
+ title="Configuration Value",
49
+ json_output=use_json
50
+ )
51
+
52
+ except typer.Exit:
53
+ raise
54
+ except Exception as e:
55
+ print_rich_error(
56
+ f"Failed to get configuration: {e}",
57
+ code="UNKNOWN_ERROR",
58
+ json_output=use_json
59
+ )
60
+ raise typer.Exit(1)
61
+
62
+
63
+ @app.command("set")
64
+ def set(
65
+ key: str = typer.Argument(..., help="Configuration key to set"),
66
+ value: str = typer.Argument(..., help="Value to set"),
67
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
68
+ ) -> None:
69
+ """Set a configuration value."""
70
+ use_json = json_output or not is_human_mode()
71
+
72
+ try:
73
+ config_path = Config.get_config_path()
74
+
75
+ # Load existing config
76
+ if config_path.exists():
77
+ with open(config_path) as f:
78
+ config_data = json.load(f)
79
+ else:
80
+ config_data = {}
81
+
82
+ # Parse value based on type
83
+ parsed_value = value
84
+ if key == "timeout" or key == "retry_attempts":
85
+ parsed_value = int(value)
86
+ elif key == "token" or key == "default_database" or key == "default_output":
87
+ pass # Keep as string
88
+
89
+ config_data[key] = parsed_value
90
+
91
+ # Save updated config
92
+ with open(config_path, "w") as f:
93
+ json.dump(config_data, f, indent=2)
94
+
95
+ print_rich_success(
96
+ f"Configuration '{key}' set to {parsed_value}",
97
+ data={"key": key, "value": parsed_value},
98
+ json_output=use_json
99
+ )
100
+
101
+ except ValueError:
102
+ print_rich_error(
103
+ f"Invalid value for '{key}'. Expected type: int for timeout/retry_attempts",
104
+ code="INVALID_VALUE",
105
+ json_output=use_json
106
+ )
107
+ raise typer.Exit(1)
108
+ except Exception as e:
109
+ print_rich_error(
110
+ f"Failed to set configuration: {e}",
111
+ code="UNKNOWN_ERROR",
112
+ json_output=use_json
113
+ )
114
+ raise typer.Exit(1)
115
+
116
+
117
+ @app.command("list")
118
+ def list_all(
119
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
120
+ ) -> None:
121
+ """List all configuration values."""
122
+ use_json = json_output or not is_human_mode()
123
+
124
+ try:
125
+ from better_notion._cli.display import print_rich_table
126
+
127
+ config = Config.load()
128
+
129
+ config_data = [
130
+ {"key": "token", "value": config.token[:20] + "..." if config.token else None},
131
+ {"key": "default_database", "value": config.default_database},
132
+ {"key": "default_output", "value": config.default_output},
133
+ {"key": "timeout", "value": config.timeout},
134
+ {"key": "retry_attempts", "value": config.retry_attempts},
135
+ ]
136
+
137
+ print_rich_table(
138
+ config_data,
139
+ title="Configuration",
140
+ columns=["key", "value"],
141
+ json_output=use_json
142
+ )
143
+
144
+ except Exception as e:
145
+ print_rich_error(
146
+ f"Failed to list configuration: {e}",
147
+ code="UNKNOWN_ERROR",
148
+ json_output=use_json
149
+ )
150
+ raise typer.Exit(1)
151
+
152
+
153
+ @app.command("reset")
154
+ def reset(
155
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
156
+ ) -> None:
157
+ """Reset configuration to defaults."""
158
+ use_json = json_output or not is_human_mode()
159
+
160
+ try:
161
+ config_path = Config.get_config_path()
162
+
163
+ if config_path.exists():
164
+ config_path.unlink()
165
+
166
+ print_rich_success(
167
+ "Configuration has been reset. Run 'notion auth login' to configure.",
168
+ data={"status": "reset"},
169
+ json_output=use_json
170
+ )
171
+
172
+ except Exception as e:
173
+ print_rich_error(
174
+ f"Failed to reset configuration: {e}",
175
+ code="UNKNOWN_ERROR",
176
+ json_output=use_json
177
+ )
178
+ raise typer.Exit(1)
@@ -25,7 +25,12 @@ import typer
25
25
  from typer.testing import CliRunner
26
26
 
27
27
  from better_notion._cli.async_typer import AsyncTyper
28
- from better_notion._cli.response import format_error, format_success
28
+ from better_notion._cli.display import (
29
+ is_human_mode,
30
+ print_rich_error,
31
+ print_rich_success,
32
+ print_rich_table,
33
+ )
29
34
  from better_notion.plugins.loader import PluginLoader
30
35
  from better_notion.plugins.base import CommandPlugin
31
36
 
@@ -220,7 +225,10 @@ def list_plugins(
220
225
  # Merge both
221
226
  all_plugins = {**official_plugins, **user_plugins}
222
227
 
223
- if json_output:
228
+ # Determine output mode
229
+ use_json = json_output or not is_human_mode()
230
+
231
+ if use_json:
224
232
  return typer.echo(json.dumps(all_plugins, indent=2))
225
233
 
226
234
  # Display as formatted output
@@ -229,41 +237,49 @@ def list_plugins(
229
237
  typer.echo(f"\nPlugins directory: {Path.home() / '.notion' / 'plugins'}")
230
238
  return
231
239
 
232
- typer.echo("Plugins:")
233
- typer.echo("=" * 70)
234
-
235
- # Group by type
236
- has_official = bool(official_plugins)
237
- has_user = bool(user_plugins)
238
-
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')}")
240
+ # Build table data for Rich display
241
+ table_data = []
242
+ for name, info in all_plugins.items():
243
+ is_bundled = info.get('bundled', False)
244
+ enabled = info.get('enabled', True) if is_bundled else True
245
+
246
+ status = "✓ Enabled" if enabled else "✗ Disabled"
247
+ if not is_bundled:
248
+ status = "User"
249
+
250
+ row = {
251
+ "name": name,
252
+ "description": info.get('description', 'No description')[:40],
253
+ "status": status,
254
+ }
255
+
256
+ if verbose:
257
+ row["version"] = info.get('version', 'unknown')
258
+ row["author"] = info.get('author', 'unknown')
259
+
260
+ table_data.append(row)
262
261
 
262
+ # Display table
263
+ columns = ["name", "description", "status"]
264
+ if verbose:
265
+ columns.extend(["version", "author"])
266
+
267
+ print_rich_table(
268
+ table_data,
269
+ title=f"Plugins ({len(all_plugins)} total)",
270
+ columns=columns,
271
+ json_output=False
272
+ )
273
+
274
+ # Show tip
263
275
  typer.echo("\nTip: Use 'notion plugin enable/disable <name>' to toggle official plugins")
264
276
 
265
277
  except Exception as e:
266
- return format_error("LIST_ERROR", str(e))
278
+ print_rich_error(
279
+ f"Failed to list plugins: {e}",
280
+ code="LIST_ERROR",
281
+ json_output=json_output or not is_human_mode()
282
+ )
267
283
 
268
284
 
269
285
  @app.command()
@@ -519,6 +535,7 @@ def validate(
519
535
  @app.command()
520
536
  def enable(
521
537
  plugin_name: str = typer.Argument(..., help="Name of the plugin to enable"),
538
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
522
539
  ) -> None:
523
540
  """
524
541
  Enable a plugin.
@@ -540,11 +557,13 @@ def enable(
540
557
  state_manager = PluginStateManager()
541
558
  state_manager.enable(plugin_name)
542
559
 
543
- return format_success({
544
- "plugin": plugin_name,
545
- "status": "enabled",
546
- "type": "official"
547
- })
560
+ use_json = json_output or not is_human_mode()
561
+ print_rich_success(
562
+ f"Plugin '{plugin_name}' enabled (may require CLI restart)",
563
+ data={"plugin": plugin_name, "type": "official"},
564
+ json_output=use_json
565
+ )
566
+ return
548
567
 
549
568
  # For user plugins, use the existing enabled.json logic
550
569
  config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
@@ -559,19 +578,26 @@ def enable(
559
578
  config["enabled"].append(plugin_name)
560
579
  config_file.write_text(json.dumps(config, indent=2))
561
580
 
562
- return format_success({
563
- "plugin": plugin_name,
564
- "status": "enabled",
565
- "type": "user"
566
- })
581
+ use_json = json_output or not is_human_mode()
582
+ print_rich_success(
583
+ f"Plugin '{plugin_name}' enabled",
584
+ data={"plugin": plugin_name, "type": "user"},
585
+ json_output=use_json
586
+ )
567
587
 
568
588
  except Exception as e:
569
- return format_error("ENABLE_ERROR", str(e))
589
+ use_json = json_output or not is_human_mode()
590
+ print_rich_error(
591
+ f"Failed to enable plugin: {e}",
592
+ code="ENABLE_ERROR",
593
+ json_output=use_json
594
+ )
570
595
 
571
596
 
572
597
  @app.command()
573
598
  def disable(
574
599
  plugin_name: str = typer.Argument(..., help="Name of the plugin to disable"),
600
+ json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
575
601
  ) -> None:
576
602
  """
577
603
  Disable a plugin.
@@ -593,12 +619,13 @@ def disable(
593
619
  state_manager = PluginStateManager()
594
620
  state_manager.disable(plugin_name)
595
621
 
596
- return format_success({
597
- "plugin": plugin_name,
598
- "status": "disabled",
599
- "type": "official",
600
- "message": "Plugin will be disabled after CLI restart"
601
- })
622
+ use_json = json_output or not is_human_mode()
623
+ print_rich_success(
624
+ f"Plugin '{plugin_name}' disabled (will take effect after CLI restart)",
625
+ data={"plugin": plugin_name, "type": "official"},
626
+ json_output=use_json
627
+ )
628
+ return
602
629
 
603
630
  # For user plugins, use the existing enabled.json logic
604
631
  config_file = Path.home() / ".notion" / "plugins" / "enabled.json"
@@ -610,17 +637,28 @@ def disable(
610
637
  config["enabled"].remove(plugin_name)
611
638
  config_file.write_text(json.dumps(config, indent=2))
612
639
 
613
- return format_success({
614
- "plugin": plugin_name,
615
- "status": "disabled",
616
- "type": "user"
617
- })
640
+ use_json = json_output or not is_human_mode()
641
+ print_rich_success(
642
+ f"Plugin '{plugin_name}' disabled",
643
+ data={"plugin": plugin_name, "type": "user"},
644
+ json_output=use_json
645
+ )
618
646
 
619
647
  except Exception as e:
620
- return format_error("DISABLE_ERROR", str(e))
648
+ use_json = json_output or not is_human_mode()
649
+ print_rich_error(
650
+ f"Failed to disable plugin: {e}",
651
+ code="DISABLE_ERROR",
652
+ json_output=use_json
653
+ )
621
654
 
622
655
  except Exception as e:
623
- return format_error("DISABLE_ERROR", str(e))
656
+ use_json = json_output or not is_human_mode()
657
+ print_rich_error(
658
+ f"Failed to disable plugin: {e}",
659
+ code="DISABLE_ERROR",
660
+ json_output=use_json
661
+ )
624
662
 
625
663
 
626
664
  @app.command()
@@ -702,8 +740,10 @@ def marketplace(
702
740
  if category:
703
741
  plugins_data = [p for p in plugins_data if p.get("category") == category]
704
742
 
705
- # Output as JSON if requested
706
- if json_output:
743
+ # Determine output mode
744
+ use_json = json_output or not is_human_mode()
745
+
746
+ if use_json:
707
747
  return typer.echo(json.dumps({"plugins": plugins_data}, indent=2))
708
748
 
709
749
  # Display in formatted table
@@ -714,42 +754,50 @@ def marketplace(
714
754
  typer.echo("No official plugins available.")
715
755
  return
716
756
 
717
- typer.echo("Official Plugins Marketplace")
718
- typer.echo("=" * 70)
719
- typer.echo(f"Found {len(plugins_data)} official plugin(s)")
720
- typer.echo()
721
-
722
- for idx, info in enumerate(plugins_data, 1):
723
- typer.echo(f"{idx}. {info.get('name', 'unknown')}")
724
- typer.echo(f" {info.get('description', 'No description')}")
725
-
726
- # Always show basic info
727
- typer.echo(f" Version: {info.get('version', 'unknown'):8} │ "
728
- f"Author: {info.get('author', 'unknown')}")
757
+ # Build table data for Rich
758
+ table_data = []
759
+ for info in plugins_data:
760
+ row = {
761
+ "name": info.get('name', 'unknown'),
762
+ "description": info.get('description', 'No description')[:40],
763
+ "version": info.get('version', 'unknown'),
764
+ }
729
765
 
730
766
  if verbose:
731
- # Show additional details in verbose mode
767
+ row["author"] = info.get('author', 'unknown')
732
768
  if info.get("category"):
733
- typer.echo(f" Category: {info.get('category')}")
734
- if info.get("dependencies"):
735
- deps = info.get("dependencies", [])
736
- if deps:
737
- typer.echo(f" Dependencies: {', '.join(deps)}")
738
- else:
739
- typer.echo(f" Dependencies: None")
740
- if info.get("official"):
741
- typer.echo(f" Official: ✓ Yes")
742
-
743
- # Show any additional metadata
744
- for key in ["license", "homepage", "repository"]:
745
- if info.get(key):
746
- typer.echo(f" {key.capitalize()}: {info.get(key)}")
747
-
748
- typer.echo()
749
-
769
+ row["category"] = info.get('category')
770
+ deps = info.get("dependencies", [])
771
+ row["dependencies"] = ', '.join(deps) if deps else "None"
772
+
773
+ table_data.append(row)
774
+
775
+ # Display table
776
+ columns = ["name", "description", "version"]
777
+ if verbose:
778
+ columns.append("author")
779
+ if any("category" in row for row in table_data):
780
+ columns.append("category")
781
+ if any("dependencies" in row for row in table_data):
782
+ columns.append("dependencies")
783
+
784
+ title = f"Official Plugins Marketplace ({len(plugins_data)} available)"
750
785
  if category:
751
- typer.echo(f"Showing plugins in category: {category}")
752
- typer.echo("\nTip: Use --verbose to see more details")
786
+ title += f" - Category: {category}"
787
+
788
+ print_rich_table(
789
+ table_data,
790
+ title=title,
791
+ columns=columns,
792
+ json_output=False
793
+ )
794
+
795
+ if not verbose:
796
+ typer.echo("\nTip: Use --verbose to see more details")
753
797
 
754
798
  except Exception as e:
755
- return format_error("MARKETPLACE_ERROR", str(e))
799
+ print_rich_error(
800
+ f"Failed to load marketplace: {e}",
801
+ code="MARKETPLACE_ERROR",
802
+ json_output=json_output or not is_human_mode()
803
+ )