supernote 0.6.2__tar.gz → 0.8.0__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 (104) hide show
  1. {supernote-0.6.2 → supernote-0.8.0}/PKG-INFO +41 -54
  2. {supernote-0.6.2 → supernote-0.8.0}/README.md +35 -53
  3. {supernote-0.6.2 → supernote-0.8.0}/pyproject.toml +6 -1
  4. {supernote-0.6.2 → supernote-0.8.0}/supernote/__init__.py +4 -2
  5. supernote-0.8.0/supernote/cli/admin.py +177 -0
  6. {supernote-0.6.2 → supernote-0.8.0}/supernote/cli/client.py +77 -42
  7. {supernote-0.6.2 → supernote-0.8.0}/supernote/cli/main.py +11 -12
  8. supernote-0.8.0/supernote/cli/server.py +42 -0
  9. supernote-0.8.0/supernote/client/admin.py +76 -0
  10. {supernote-0.6.2 → supernote-0.8.0}/supernote/client/auth.py +30 -18
  11. supernote-0.8.0/supernote/client/client.py +357 -0
  12. {supernote-0.6.2 → supernote-0.8.0}/supernote/client/cloud_client.py +19 -19
  13. supernote-0.8.0/supernote/client/device.py +309 -0
  14. {supernote-0.6.2 → supernote-0.8.0}/supernote/client/exceptions.py +8 -0
  15. supernote-0.8.0/supernote/client/hashing.py +50 -0
  16. supernote-0.8.0/supernote/client/login_client.py +154 -0
  17. supernote-0.8.0/supernote/client/schedule.py +149 -0
  18. supernote-0.8.0/supernote/client/web.py +208 -0
  19. supernote-0.8.0/supernote/models/__init__.py +14 -0
  20. supernote-0.8.0/supernote/models/auth.py +380 -0
  21. supernote-0.8.0/supernote/models/base.py +256 -0
  22. supernote-0.8.0/supernote/models/equipment.py +276 -0
  23. supernote-0.8.0/supernote/models/file_common.py +94 -0
  24. supernote-0.8.0/supernote/models/file_device.py +505 -0
  25. supernote-0.8.0/supernote/models/file_web.py +602 -0
  26. supernote-0.8.0/supernote/models/schedule.py +663 -0
  27. supernote-0.8.0/supernote/models/summary.py +591 -0
  28. supernote-0.8.0/supernote/models/system.py +433 -0
  29. supernote-0.8.0/supernote/models/user.py +638 -0
  30. supernote-0.8.0/supernote/server/app.py +270 -0
  31. supernote-0.8.0/supernote/server/config.py +124 -0
  32. supernote-0.8.0/supernote/server/constants.py +19 -0
  33. supernote-0.8.0/supernote/server/db/__init__.py +9 -0
  34. supernote-0.8.0/supernote/server/db/base.py +8 -0
  35. supernote-0.8.0/supernote/server/db/models/__init__.py +12 -0
  36. supernote-0.8.0/supernote/server/db/models/device.py +17 -0
  37. supernote-0.8.0/supernote/server/db/models/file.py +104 -0
  38. supernote-0.8.0/supernote/server/db/models/kv.py +17 -0
  39. supernote-0.8.0/supernote/server/db/models/login_record.py +22 -0
  40. supernote-0.8.0/supernote/server/db/models/schedule.py +83 -0
  41. supernote-0.8.0/supernote/server/db/models/user.py +30 -0
  42. supernote-0.8.0/supernote/server/db/session.py +88 -0
  43. supernote-0.8.0/supernote/server/exceptions.py +140 -0
  44. supernote-0.8.0/supernote/server/routes/admin.py +109 -0
  45. supernote-0.8.0/supernote/server/routes/auth.py +336 -0
  46. supernote-0.8.0/supernote/server/routes/file_device.py +515 -0
  47. supernote-0.8.0/supernote/server/routes/file_web.py +597 -0
  48. supernote-0.8.0/supernote/server/routes/oss.py +275 -0
  49. supernote-0.8.0/supernote/server/routes/schedule.py +223 -0
  50. supernote-0.8.0/supernote/server/routes/system.py +57 -0
  51. supernote-0.8.0/supernote/server/services/__init__.py +15 -0
  52. supernote-0.8.0/supernote/server/services/blob.py +147 -0
  53. supernote-0.8.0/supernote/server/services/coordination.py +176 -0
  54. supernote-0.8.0/supernote/server/services/file.py +954 -0
  55. supernote-0.8.0/supernote/server/services/integrity.py +91 -0
  56. supernote-0.8.0/supernote/server/services/schedule.py +161 -0
  57. supernote-0.8.0/supernote/server/services/user.py +475 -0
  58. supernote-0.8.0/supernote/server/services/vfs.py +468 -0
  59. supernote-0.8.0/supernote/server/utils/hashing.py +13 -0
  60. supernote-0.8.0/supernote/server/utils/rate_limit.py +52 -0
  61. supernote-0.8.0/supernote/server/utils/unique_id.py +48 -0
  62. supernote-0.8.0/supernote/server/utils/url_signer.py +193 -0
  63. {supernote-0.6.2 → supernote-0.8.0}/supernote.egg-info/PKG-INFO +41 -54
  64. supernote-0.8.0/supernote.egg-info/SOURCES.txt +84 -0
  65. {supernote-0.6.2 → supernote-0.8.0}/supernote.egg-info/requires.txt +5 -0
  66. supernote-0.6.2/supernote/cli/server.py +0 -137
  67. supernote-0.6.2/supernote/client/client.py +0 -184
  68. supernote-0.6.2/supernote/client/login_client.py +0 -174
  69. supernote-0.6.2/supernote/server/app.py +0 -143
  70. supernote-0.6.2/supernote/server/config.py +0 -90
  71. supernote-0.6.2/supernote/server/models/__init__.py +0 -1
  72. supernote-0.6.2/supernote/server/models/auth.py +0 -139
  73. supernote-0.6.2/supernote/server/models/base.py +0 -30
  74. supernote-0.6.2/supernote/server/models/file.py +0 -361
  75. supernote-0.6.2/supernote/server/routes/auth.py +0 -155
  76. supernote-0.6.2/supernote/server/routes/file.py +0 -476
  77. supernote-0.6.2/supernote/server/routes/system.py +0 -33
  78. supernote-0.6.2/supernote/server/services/__init__.py +0 -3
  79. supernote-0.6.2/supernote/server/services/file.py +0 -347
  80. supernote-0.6.2/supernote/server/services/state.py +0 -107
  81. supernote-0.6.2/supernote/server/services/storage.py +0 -323
  82. supernote-0.6.2/supernote/server/services/user.py +0 -156
  83. supernote-0.6.2/supernote.egg-info/SOURCES.txt +0 -48
  84. {supernote-0.6.2 → supernote-0.8.0}/LICENSE +0 -0
  85. {supernote-0.6.2 → supernote-0.8.0}/setup.cfg +0 -0
  86. {supernote-0.6.2 → supernote-0.8.0}/supernote/cli/__init__.py +0 -0
  87. {supernote-0.6.2 → supernote-0.8.0}/supernote/cli/notebook.py +0 -0
  88. {supernote-0.6.2 → supernote-0.8.0}/supernote/client/__init__.py +0 -0
  89. {supernote-0.6.2 → supernote-0.8.0}/supernote/client/api_model.py +0 -0
  90. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/__init__.py +0 -0
  91. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/color.py +0 -0
  92. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/converter.py +0 -0
  93. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/decoder.py +0 -0
  94. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/exceptions.py +0 -0
  95. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/fileformat.py +0 -0
  96. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/manipulator.py +0 -0
  97. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/parser.py +0 -0
  98. {supernote-0.6.2 → supernote-0.8.0}/supernote/notebook/utils.py +0 -0
  99. {supernote-0.6.2 → supernote-0.8.0}/supernote/py.typed +0 -0
  100. {supernote-0.6.2 → supernote-0.8.0}/supernote/server/__init__.py +0 -0
  101. {supernote-0.6.2 → supernote-0.8.0}/supernote/server/routes/decorators.py +0 -0
  102. {supernote-0.6.2 → supernote-0.8.0}/supernote.egg-info/dependency_links.txt +0 -0
  103. {supernote-0.6.2 → supernote-0.8.0}/supernote.egg-info/entry_points.txt +0 -0
  104. {supernote-0.6.2 → supernote-0.8.0}/supernote.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supernote
3
- Version: 0.6.2
3
+ Version: 0.8.0
4
4
  Summary: All-in-one toolkit for Supernote devices: parse notebooks, self-host services, access services
5
5
  Author-email: jya <jya@wizmy.net>, Allen Porter <allen.porter@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -24,6 +24,11 @@ Requires-Dist: aiohttp>=3.13.2; extra == "server"
24
24
  Requires-Dist: mashumaro>=3.17; extra == "server"
25
25
  Requires-Dist: pyyaml>=6.0; extra == "server"
26
26
  Requires-Dist: PyJWT>=2.10.1; extra == "server"
27
+ Requires-Dist: SQLAlchemy[asyncio]>=2.0.0; extra == "server"
28
+ Requires-Dist: alembic>=1.13.0; extra == "server"
29
+ Requires-Dist: aiosqlite>=0.19.0; extra == "server"
30
+ Requires-Dist: aiofiles>=25.1.0; extra == "server"
31
+ Requires-Dist: aiohttp-remotes>=1.3.0; extra == "server"
27
32
  Provides-Extra: all
28
33
  Requires-Dist: supernote[client,server]; extra == "all"
29
34
  Dynamic: license-file
@@ -38,7 +43,7 @@ This content is shared between the [documentation](https://allenporter.github.io
38
43
 
39
44
  - **Notebook Parsing**: Convert `.note` files to PDF, PNG, SVG, or text
40
45
  - **Private Server**: Self-hosted Supernote Private Cloud implementation
41
- - **Client**: Interact with Supernote service API
46
+ - **Client**: Interact with Supernote (Private) Cloud API
42
47
 
43
48
  ## Installation
44
49
 
@@ -53,16 +58,9 @@ pip install supernote[client] # + Client
53
58
  pip install supernote[all]
54
59
  ```
55
60
 
56
- ## Local Development
61
+ ## Local Development Setup
57
62
 
58
- Install in a virtual environment:
59
-
60
- ```bash
61
- uv venv --python=3.14
62
- source .venv/bin/activate
63
- uv pip install -r requirements_dev.txt
64
- uv pip install -e ".[all]"
65
- ```
63
+ To set up the project for development, please refer to the [Contributing Guide](docs/CONTRIBUTING.md).
66
64
 
67
65
  ## Quick Start
68
66
 
@@ -79,38 +77,37 @@ The notebook parser is a fork and slightly lighter dependency version of [supern
79
77
  limitation. All credit goes to the original authors of [supernote-tool](https://github.com/jya-dev/supernote-tool) for providing an amazing low level utility.
80
78
 
81
79
 
82
- ### Run Private Server
80
+ ### Server Setup & Bootstrap
83
81
 
84
- ```bash
85
- # Generate configuration
86
- supernote-server config init > config.yaml
82
+ 1. **Start the Server**:
83
+ ```bash
84
+ supernote serve
85
+ ```
87
86
 
88
- # Generate users (outputs YAML, append to users.yaml)
89
- supernote-server user add alice >> users.yaml
87
+ 2. **Register the Admin**:
88
+ The first user registered becomes the system administrator.
89
+ ```bash
90
+ supernote admin user add email@example.com --url http://localhost:8080
91
+ ```
90
92
 
91
- # Start server
92
- supernote-server serve
93
- ```
93
+ 3. **Login to CLI**:
94
+ ```bash
95
+ supernote cloud-login email@example.com --url http://localhost:8080
96
+ ```
97
+
98
+ See the [Bootstrap Guide](docs/bootstrap_guide.md) for detailed deployment and security instructions.
94
99
 
95
100
  ### Run with Docker
96
101
 
97
102
  ```bash
98
103
  # Build image
99
- docker build -t supernote-server .
100
-
101
- # Generate initial configuration
102
- mkdir config
103
- docker run --rm supernote-server supernote-server config init > config/config.yaml
104
- docker run --rm -it supernote-server supernote-server user add alice >> config/users.yaml
104
+ docker build -t supernote .
105
105
 
106
106
  # Run server
107
- docker run -d -p 8080:8080 \
108
- -v $(pwd)/config:/config \
109
- -v $(pwd)/storage:/data \
110
- supernote-server
107
+ docker run -d -p 8080:8080 -v $(pwd)/storage:/storage supernote serve
111
108
  ```
112
109
 
113
- See [Server Documentation](supernote/server/README.md) for details.
110
+ See [Server Documentation](https://github.com/allenporter/supernote-lite/blob/main/supernote/server/README.mdd) for details.
114
111
 
115
112
 
116
113
  ### Access Supernote Services
@@ -128,36 +125,26 @@ async with SupernoteClient.from_credentials(email, password) as client:
128
125
 
129
126
  ```bash
130
127
  # Notebook operations
131
- supernote convert input.note output.pdf
132
- supernote analyze input.note
128
+ supernote notebook convert input.note output.pdf
129
+ supernote notebook analyze input.note
133
130
 
134
131
  # Server operations
135
- supernote-server serve
136
- supernote-server user add alice
137
-
132
+ supernote serve
133
+ supernote admin user list
134
+ supernote admin user add email@example.com
138
135
 
139
136
  # Client operations
140
- supernote client login
141
- supernote client ls
137
+ supernote cloud-login --url http://localhost:8080 email@example.com
138
+ supernote cloud-ls
142
139
  ```
143
140
 
144
- ## Development
145
-
146
- This package is designed for:
147
- 1. **Server operators** - Self-hosting Supernote Private Cloud
148
- 2. **Developers** - Integrating Supernote into applications
149
- 3. **Reference** - Understanding Supernote protocols
150
-
151
- See [ARCHITECTURE.md](supernote/server/ARCHITECTURE.md) for protocol details.
141
+ ## Contributing
152
142
 
153
- ### Setup
154
-
155
- ```bash
156
- uv venv --python=3.14
157
- source .venv/bin/activate
158
- uv pip install -r requirements_dev.txt
159
- uv pip install -e ".[all]"
160
- ```
143
+ We welcome contributions! Please see our [Contributing Guide](docs/CONTRIBUTING.md) for details on:
144
+ - Local development setup
145
+ - Project architecture
146
+ - Coding standards
147
+ - Running tests
161
148
 
162
149
  ## Credits
163
150
 
@@ -8,7 +8,7 @@ This content is shared between the [documentation](https://allenporter.github.io
8
8
 
9
9
  - **Notebook Parsing**: Convert `.note` files to PDF, PNG, SVG, or text
10
10
  - **Private Server**: Self-hosted Supernote Private Cloud implementation
11
- - **Client**: Interact with Supernote service API
11
+ - **Client**: Interact with Supernote (Private) Cloud API
12
12
 
13
13
  ## Installation
14
14
 
@@ -23,16 +23,9 @@ pip install supernote[client] # + Client
23
23
  pip install supernote[all]
24
24
  ```
25
25
 
26
- ## Local Development
26
+ ## Local Development Setup
27
27
 
28
- Install in a virtual environment:
29
-
30
- ```bash
31
- uv venv --python=3.14
32
- source .venv/bin/activate
33
- uv pip install -r requirements_dev.txt
34
- uv pip install -e ".[all]"
35
- ```
28
+ To set up the project for development, please refer to the [Contributing Guide](docs/CONTRIBUTING.md).
36
29
 
37
30
  ## Quick Start
38
31
 
@@ -49,38 +42,37 @@ The notebook parser is a fork and slightly lighter dependency version of [supern
49
42
  limitation. All credit goes to the original authors of [supernote-tool](https://github.com/jya-dev/supernote-tool) for providing an amazing low level utility.
50
43
 
51
44
 
52
- ### Run Private Server
45
+ ### Server Setup & Bootstrap
53
46
 
54
- ```bash
55
- # Generate configuration
56
- supernote-server config init > config.yaml
47
+ 1. **Start the Server**:
48
+ ```bash
49
+ supernote serve
50
+ ```
57
51
 
58
- # Generate users (outputs YAML, append to users.yaml)
59
- supernote-server user add alice >> users.yaml
52
+ 2. **Register the Admin**:
53
+ The first user registered becomes the system administrator.
54
+ ```bash
55
+ supernote admin user add email@example.com --url http://localhost:8080
56
+ ```
60
57
 
61
- # Start server
62
- supernote-server serve
63
- ```
58
+ 3. **Login to CLI**:
59
+ ```bash
60
+ supernote cloud-login email@example.com --url http://localhost:8080
61
+ ```
62
+
63
+ See the [Bootstrap Guide](docs/bootstrap_guide.md) for detailed deployment and security instructions.
64
64
 
65
65
  ### Run with Docker
66
66
 
67
67
  ```bash
68
68
  # Build image
69
- docker build -t supernote-server .
70
-
71
- # Generate initial configuration
72
- mkdir config
73
- docker run --rm supernote-server supernote-server config init > config/config.yaml
74
- docker run --rm -it supernote-server supernote-server user add alice >> config/users.yaml
69
+ docker build -t supernote .
75
70
 
76
71
  # Run server
77
- docker run -d -p 8080:8080 \
78
- -v $(pwd)/config:/config \
79
- -v $(pwd)/storage:/data \
80
- supernote-server
72
+ docker run -d -p 8080:8080 -v $(pwd)/storage:/storage supernote serve
81
73
  ```
82
74
 
83
- See [Server Documentation](supernote/server/README.md) for details.
75
+ See [Server Documentation](https://github.com/allenporter/supernote-lite/blob/main/supernote/server/README.mdd) for details.
84
76
 
85
77
 
86
78
  ### Access Supernote Services
@@ -98,36 +90,26 @@ async with SupernoteClient.from_credentials(email, password) as client:
98
90
 
99
91
  ```bash
100
92
  # Notebook operations
101
- supernote convert input.note output.pdf
102
- supernote analyze input.note
93
+ supernote notebook convert input.note output.pdf
94
+ supernote notebook analyze input.note
103
95
 
104
96
  # Server operations
105
- supernote-server serve
106
- supernote-server user add alice
107
-
97
+ supernote serve
98
+ supernote admin user list
99
+ supernote admin user add email@example.com
108
100
 
109
101
  # Client operations
110
- supernote client login
111
- supernote client ls
102
+ supernote cloud-login --url http://localhost:8080 email@example.com
103
+ supernote cloud-ls
112
104
  ```
113
105
 
114
- ## Development
115
-
116
- This package is designed for:
117
- 1. **Server operators** - Self-hosting Supernote Private Cloud
118
- 2. **Developers** - Integrating Supernote into applications
119
- 3. **Reference** - Understanding Supernote protocols
120
-
121
- See [ARCHITECTURE.md](supernote/server/ARCHITECTURE.md) for protocol details.
106
+ ## Contributing
122
107
 
123
- ### Setup
124
-
125
- ```bash
126
- uv venv --python=3.14
127
- source .venv/bin/activate
128
- uv pip install -r requirements_dev.txt
129
- uv pip install -e ".[all]"
130
- ```
108
+ We welcome contributions! Please see our [Contributing Guide](docs/CONTRIBUTING.md) for details on:
109
+ - Local development setup
110
+ - Project architecture
111
+ - Coding standards
112
+ - Running tests
131
113
 
132
114
  ## Credits
133
115
 
@@ -4,7 +4,7 @@ requires = ["setuptools>=77.0"]
4
4
 
5
5
  [project]
6
6
  name = "supernote"
7
- version = "0.6.2"
7
+ version = "0.8.0"
8
8
  license = "Apache-2.0"
9
9
  license-files = ["LICENSE"]
10
10
  description = "All-in-one toolkit for Supernote devices: parse notebooks, self-host services, access services"
@@ -37,6 +37,11 @@ server = [
37
37
  "mashumaro>=3.17",
38
38
  "pyyaml>=6.0",
39
39
  "PyJWT>=2.10.1",
40
+ "SQLAlchemy[asyncio]>=2.0.0",
41
+ "alembic>=1.13.0",
42
+ "aiosqlite>=0.19.0",
43
+ "aiofiles>=25.1.0",
44
+ "aiohttp-remotes>=1.3.0",
40
45
  ]
41
46
  all = ["supernote[client,server]"]
42
47
 
@@ -3,10 +3,10 @@
3
3
  """
4
4
 
5
5
  # Core notebook parsing (always available)
6
- from . import notebook
6
+ from . import notebook # noqa: F401
7
7
 
8
8
  __all__ = [
9
- "notebook",
9
+ "models",
10
10
  ]
11
11
 
12
12
  # Optional: Client library
@@ -24,3 +24,5 @@ try:
24
24
  __all__.extend(["server"])
25
25
  except ImportError:
26
26
  pass
27
+
28
+ __all__.extend(["notebook"])
@@ -0,0 +1,177 @@
1
+ """Admin CLI commands."""
2
+
3
+ import asyncio
4
+ import getpass
5
+ import hashlib
6
+ import sys
7
+
8
+ from supernote.client.admin import AdminClient
9
+ from supernote.client.exceptions import ApiException
10
+
11
+ from .client import create_client
12
+
13
+
14
+ async def add_user_async(
15
+ url: str, email: str, password: str, display_name: str | None = None
16
+ ):
17
+ """Async implementation of add user."""
18
+ async with create_client(url) as client:
19
+ print(f"Attempting to register user '{email}' on {client.host}...")
20
+
21
+ admin_client = AdminClient(client)
22
+
23
+ # Hash password
24
+ password_md5 = hashlib.md5(password.encode()).hexdigest()
25
+
26
+ # Try Public Registration
27
+ try:
28
+ await admin_client.register(
29
+ email=email,
30
+ password=password_md5,
31
+ username=display_name,
32
+ )
33
+ print("Success! User created (Public Registration).")
34
+ return
35
+ except ApiException:
36
+ # If failed, we don't have detailed status code in simple try/except unless we check exception type
37
+ # But client raises ApiException
38
+ pass
39
+
40
+ # Try Admin Creation API
41
+ print("Public registration failed or disabled. Attempting Admin creation...")
42
+ try:
43
+ await admin_client.admin_create_user(
44
+ email=email,
45
+ password=password_md5,
46
+ username=display_name,
47
+ )
48
+ print("Success! User created (Admin API).")
49
+ except Exception as e:
50
+ print(f"Admin creation failed: {e}")
51
+ sys.exit(1)
52
+
53
+
54
+ async def list_users_async():
55
+ """Async implementation of list users."""
56
+ async with create_client() as client:
57
+ try:
58
+ users = await client.get_json("/api/admin/users", list)
59
+
60
+ print(f"\nTotal Users: {len(users)}\n")
61
+ print(f"{'Username':<30} {'Email':<30} {'Capacity':<10}")
62
+ print("-" * 75)
63
+ for u in users:
64
+ # users is a list of dicts if we passed list to get_json, or we need a VO
65
+ # client.get_json returns data_cls.from_json(result)
66
+ # But list doesn't have from_json.
67
+ # We should probably use raw get or define a List[UserVO] type if we can.
68
+ # For now let's just use raw request to get json list safely.
69
+ pass
70
+ except Exception:
71
+ pass
72
+
73
+ # Re-doing list fetch with raw request because of typing limitation in client.get_json for list[dict]
74
+ try:
75
+ resp = await client.get("/api/admin/users")
76
+ users = await resp.json()
77
+
78
+ print(f"\nTotal Users: {len(users)}\n")
79
+ print(f"{'Email':<30} {'Name':<20} {'Capacity':<10}")
80
+ print("-" * 65)
81
+ for u in users:
82
+ print(
83
+ f"{u.get('email', 'N/A'):<30} {u.get('userName', 'N/A'):<20} {u.get('totalCapacity', '0'):<10}"
84
+ )
85
+
86
+ except Exception as e:
87
+ print(f"Failed to list users: {e}")
88
+
89
+
90
+ def add_user(args):
91
+ password = args.password
92
+ if not password:
93
+ password = getpass.getpass(f"Password for {args.email}: ")
94
+
95
+ display_name = args.name or args.email.split("@")[0]
96
+ asyncio.run(add_user_async(args.url, args.email, password, display_name))
97
+
98
+
99
+ def list_users(args):
100
+ asyncio.run(list_users_async())
101
+
102
+
103
+ async def reset_password_async(url: str, email: str, password: str):
104
+ """Async implementation of password reset."""
105
+ async with create_client(url) as client:
106
+ print(f"Attempting to reset password for '{email}' on {client.host}...")
107
+ admin_client = AdminClient(client)
108
+
109
+ password_md5 = hashlib.md5(password.encode()).hexdigest()
110
+
111
+ try:
112
+ await admin_client.admin_reset_password(email, password_md5)
113
+ print("Success! Password Reset.")
114
+ except Exception as e:
115
+ print(f"Failed to reset password: {e}")
116
+ sys.exit(1)
117
+
118
+
119
+ def reset_password(args):
120
+ password = args.password
121
+ if not password:
122
+ password = getpass.getpass(f"New password for {args.email}: ")
123
+ confirm = getpass.getpass("Confirm password: ")
124
+ if password != confirm:
125
+ print("Passwords do not match.")
126
+ sys.exit(1)
127
+
128
+ asyncio.run(reset_password_async(args.url, args.email, password))
129
+
130
+
131
+ def add_parser(subparsers):
132
+ # 'admin' parent command
133
+ parser_admin = subparsers.add_parser(
134
+ "admin",
135
+ help="Administrative commands for Supernote Server",
136
+ )
137
+ parser_admin.add_argument("--url", type=str, help="URL of the Supernote Server")
138
+
139
+ admin_subparsers = parser_admin.add_subparsers(dest="admin_command")
140
+
141
+ # --- User Management ---
142
+ parser_user = admin_subparsers.add_parser("user", help="User management commands")
143
+ user_subparsers = parser_user.add_subparsers(dest="user_command")
144
+
145
+ # user list
146
+ parser_user_list = user_subparsers.add_parser(
147
+ "list",
148
+ help="List all users",
149
+ )
150
+ parser_user_list.set_defaults(func=list_users)
151
+
152
+ # user add
153
+ parser_user_add = user_subparsers.add_parser(
154
+ "add",
155
+ help="Add a new user",
156
+ )
157
+ parser_user_add.add_argument(
158
+ "email", type=str, help="Email address for the new user"
159
+ )
160
+ parser_user_add.add_argument(
161
+ "--password", type=str, help="Password (if omitted, prompt interactively)"
162
+ )
163
+ parser_user_add.add_argument(
164
+ "--name", type=str, help="Display name (optional)", default=None
165
+ )
166
+ parser_user_add.set_defaults(func=add_user)
167
+
168
+ # user reset-password
169
+ parser_user_reset = user_subparsers.add_parser(
170
+ "reset-password",
171
+ help="Reset user password",
172
+ )
173
+ parser_user_reset.add_argument("email", type=str, help="Email address of the user")
174
+ parser_user_reset.add_argument(
175
+ "--password", type=str, help="New password (if omitted, prompt interactively)"
176
+ )
177
+ parser_user_reset.set_defaults(func=reset_password)