promptready-mcp 0.3.3__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.
- promptready_mcp-0.3.3/LICENSE +27 -0
- promptready_mcp-0.3.3/MANIFEST.in +1 -0
- promptready_mcp-0.3.3/PKG-INFO +245 -0
- promptready_mcp-0.3.3/README.md +215 -0
- promptready_mcp-0.3.3/promptready_mcp/__init__.py +7 -0
- promptready_mcp-0.3.3/promptready_mcp/client.py +166 -0
- promptready_mcp-0.3.3/promptready_mcp/config.py +55 -0
- promptready_mcp-0.3.3/promptready_mcp/login_flow.py +341 -0
- promptready_mcp-0.3.3/promptready_mcp/server.py +489 -0
- promptready_mcp-0.3.3/promptready_mcp/token_store.py +89 -0
- promptready_mcp-0.3.3/promptready_mcp/user_settings.py +121 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/PKG-INFO +245 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/SOURCES.txt +17 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/dependency_links.txt +1 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/entry_points.txt +3 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/requires.txt +2 -0
- promptready_mcp-0.3.3/promptready_mcp.egg-info/top_level.txt +1 -0
- promptready_mcp-0.3.3/pyproject.toml +58 -0
- promptready_mcp-0.3.3/setup.cfg +4 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PromptReady / HydRoMo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Note: This license applies to the **PromptReady MCP client** source code only.
|
|
26
|
+
Use of the PromptReady cloud service (https://promptready.space) remains
|
|
27
|
+
subject to the PromptReady Terms of Service and Privacy Policy.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prune tests
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: promptready-mcp
|
|
3
|
+
Version: 0.3.3
|
|
4
|
+
Summary: MCP server (stdio) for PromptReady — convert PDF/CSV to Markdown from AI agents.
|
|
5
|
+
Author: PromptReady
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://promptready.space
|
|
8
|
+
Project-URL: Documentation, https://github.com/hydrojwh/promptready-mcp#readme
|
|
9
|
+
Project-URL: Source, https://github.com/hydrojwh/promptready-mcp
|
|
10
|
+
Project-URL: Issues, https://github.com/hydrojwh/promptready-mcp/issues
|
|
11
|
+
Keywords: mcp,model-context-protocol,pdf,csv,markdown,ocr,claude,cursor
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: mcp<2,>=1.2.0
|
|
28
|
+
Requires-Dist: httpx<1,>=0.27
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# PromptReady MCP
|
|
32
|
+
|
|
33
|
+
<!-- mcp-name: io.github.hydrojwh/promptready-mcp -->
|
|
34
|
+
|
|
35
|
+
Official [Model Context Protocol](https://modelcontextprotocol.io/) client for
|
|
36
|
+
[PromptReady](https://promptready.space) — convert PDF/CSV to Markdown from AI
|
|
37
|
+
agents (Grok, Claude Code, Cursor, and other MCP hosts).
|
|
38
|
+
|
|
39
|
+
**Same PromptReady account and credits as the web app.**
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Browser Google login (tokens stay on your machine)
|
|
44
|
+
- `get_credits`, `convert_pdf`, `get_status`, `wait_and_download`
|
|
45
|
+
- Saved convert defaults (engine, tables, images) — not on every call
|
|
46
|
+
- Factory default: **PaddleOCR-VL**, tables on, images off
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install promptready-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or run it without installing:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uvx promptready-mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
<details>
|
|
61
|
+
<summary>From source</summary>
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/hydrojwh/promptready-mcp.git
|
|
65
|
+
cd promptready-mcp
|
|
66
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
</details>
|
|
71
|
+
|
|
72
|
+
## Login (once per machine)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
promptready-mcp-login
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you installed with `uvx`, the login command lives in the same package:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uvx --from promptready-mcp promptready-mcp-login
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Either opens Google OAuth and saves credentials to
|
|
85
|
+
`~/.config/promptready/credentials.json` (file mode `0600`). That path is in your
|
|
86
|
+
home directory, so it survives `uvx` cache resets. You can also log in from inside
|
|
87
|
+
an MCP host by calling the `login` tool.
|
|
88
|
+
|
|
89
|
+
Supabase Auth must allow redirect:
|
|
90
|
+
|
|
91
|
+
`http://127.0.0.1:18765/callback`
|
|
92
|
+
|
|
93
|
+
<details>
|
|
94
|
+
<summary>Email login fallback</summary>
|
|
95
|
+
|
|
96
|
+
If browser-based Google login is not an option, the same command accepts email
|
|
97
|
+
and password:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
promptready-mcp-login --email you@x.com
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Leave out `--password` and you will be prompted for it instead — this keeps the
|
|
104
|
+
password out of your shell history.
|
|
105
|
+
|
|
106
|
+
</details>
|
|
107
|
+
|
|
108
|
+
Already have an access token? Set `PROMPTREADY_ACCESS_TOKEN` in the environment
|
|
109
|
+
(MCP host or shell). Environment variables take precedence over the saved
|
|
110
|
+
credentials file.
|
|
111
|
+
|
|
112
|
+
## MCP host config
|
|
113
|
+
|
|
114
|
+
### Fastest: let your AI agent install it
|
|
115
|
+
|
|
116
|
+
If you are already in an MCP-capable agent, skip the JSON editing and just
|
|
117
|
+
ask:
|
|
118
|
+
|
|
119
|
+
> Install the PromptReady MCP server for me. The PyPI package is
|
|
120
|
+
> `promptready-mcp` (stdio command `promptready-mcp`). Add it to your MCP
|
|
121
|
+
> config, then I will run the `login` tool.
|
|
122
|
+
|
|
123
|
+
In Claude Code the agent can use the built-in CLI:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
claude mcp add promptready -- promptready-mcp
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Reconnect after changing config
|
|
130
|
+
|
|
131
|
+
Hosts do not pick up MCP config changes mid-session. After changing the
|
|
132
|
+
config, restart the host or reconnect the server — in Claude Code, open the
|
|
133
|
+
`/mcp` panel and reconnect.
|
|
134
|
+
|
|
135
|
+
Seeing tools in the `/mcp` panel does **not** mean the server is connected:
|
|
136
|
+
the panel can list the tool catalog while the session has no live server,
|
|
137
|
+
and picking a tool from that list will not run it. If tools are listed but
|
|
138
|
+
calls fail, reconnect first.
|
|
139
|
+
|
|
140
|
+
### Claude Code
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
claude mcp add promptready -- promptready-mcp
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The default scope is `local` (this project only). Use `--scope user` to
|
|
147
|
+
register it for all your projects, or `--scope project` to share the
|
|
148
|
+
registration through a committed `.mcp.json`.
|
|
149
|
+
|
|
150
|
+
### Cursor
|
|
151
|
+
|
|
152
|
+
Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` for a single project):
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"mcpServers": {
|
|
157
|
+
"promptready": {
|
|
158
|
+
"command": "promptready-mcp"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Grok
|
|
165
|
+
|
|
166
|
+
```toml
|
|
167
|
+
[mcp_servers.promptready]
|
|
168
|
+
command = "promptready-mcp"
|
|
169
|
+
enabled = true
|
|
170
|
+
tool_timeout_sec = 3600
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Claude Desktop / generic JSON
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"mcpServers": {
|
|
178
|
+
"promptready": {
|
|
179
|
+
"command": "promptready-mcp"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
No access token in config files required after login.
|
|
186
|
+
|
|
187
|
+
<details>
|
|
188
|
+
<summary>Host cannot find <code>promptready-mcp</code></summary>
|
|
189
|
+
|
|
190
|
+
GUI hosts start servers with a narrow `PATH`, so a console script installed by
|
|
191
|
+
`pip install --user` is often invisible to them — the host reports a spawn
|
|
192
|
+
failure or "server disconnected" rather than a missing command.
|
|
193
|
+
|
|
194
|
+
Two reliable fixes:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{ "mcpServers": { "promptready": {
|
|
198
|
+
"command": "uvx", "args": ["promptready-mcp"] } } }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
or point at the absolute path of the script:
|
|
202
|
+
`/ABS/PATH/.venv/bin/promptready-mcp`.
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
## Tools
|
|
207
|
+
|
|
208
|
+
| Tool | Purpose |
|
|
209
|
+
|------|---------|
|
|
210
|
+
| `login` / `logout` | Browser auth / clear local credentials |
|
|
211
|
+
| `get_credits` | Credit balance |
|
|
212
|
+
| `get_convert_settings` / `set_convert_settings` | Saved convert defaults |
|
|
213
|
+
| `convert_pdf` | Upload path → queue (optional `wait`) |
|
|
214
|
+
| `get_status` | Job status |
|
|
215
|
+
| `wait_and_download` | Poll + save `.md` |
|
|
216
|
+
|
|
217
|
+
Downloaded names follow the web app: `{name}_PaddleOCR-VL.md` (engine label).
|
|
218
|
+
|
|
219
|
+
Credits are deducted by the server when a job is queued, exactly as on the web
|
|
220
|
+
app. `convert_pdf(wait=True)` can run for a long time, so give the host a high
|
|
221
|
+
tool timeout.
|
|
222
|
+
|
|
223
|
+
## Security
|
|
224
|
+
|
|
225
|
+
- Tokens are **never** hardcoded in this repository.
|
|
226
|
+
- Do **not** commit `~/.config/promptready/*` or `.env`.
|
|
227
|
+
- Only use the official package linked from https://promptready.space
|
|
228
|
+
- Vulnerability reports: see [SECURITY.md](SECURITY.md)
|
|
229
|
+
|
|
230
|
+
## Service terms
|
|
231
|
+
|
|
232
|
+
Using the cloud API is subject to the
|
|
233
|
+
[Terms of Service](https://promptready.space/legal/terms-of-service.en.md) and
|
|
234
|
+
[Privacy Policy](https://promptready.space/legal/privacy-policy.en.md).
|
|
235
|
+
This MIT-licensed client does not grant free unlimited conversion.
|
|
236
|
+
|
|
237
|
+
## Smoke test (no account)
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
./scripts/smoke_stdio.sh
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# PromptReady MCP
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.hydrojwh/promptready-mcp -->
|
|
4
|
+
|
|
5
|
+
Official [Model Context Protocol](https://modelcontextprotocol.io/) client for
|
|
6
|
+
[PromptReady](https://promptready.space) — convert PDF/CSV to Markdown from AI
|
|
7
|
+
agents (Grok, Claude Code, Cursor, and other MCP hosts).
|
|
8
|
+
|
|
9
|
+
**Same PromptReady account and credits as the web app.**
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Browser Google login (tokens stay on your machine)
|
|
14
|
+
- `get_credits`, `convert_pdf`, `get_status`, `wait_and_download`
|
|
15
|
+
- Saved convert defaults (engine, tables, images) — not on every call
|
|
16
|
+
- Factory default: **PaddleOCR-VL**, tables on, images off
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install promptready-mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or run it without installing:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx promptready-mcp
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary>From source</summary>
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/hydrojwh/promptready-mcp.git
|
|
35
|
+
cd promptready-mcp
|
|
36
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
</details>
|
|
41
|
+
|
|
42
|
+
## Login (once per machine)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
promptready-mcp-login
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you installed with `uvx`, the login command lives in the same package:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx --from promptready-mcp promptready-mcp-login
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Either opens Google OAuth and saves credentials to
|
|
55
|
+
`~/.config/promptready/credentials.json` (file mode `0600`). That path is in your
|
|
56
|
+
home directory, so it survives `uvx` cache resets. You can also log in from inside
|
|
57
|
+
an MCP host by calling the `login` tool.
|
|
58
|
+
|
|
59
|
+
Supabase Auth must allow redirect:
|
|
60
|
+
|
|
61
|
+
`http://127.0.0.1:18765/callback`
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary>Email login fallback</summary>
|
|
65
|
+
|
|
66
|
+
If browser-based Google login is not an option, the same command accepts email
|
|
67
|
+
and password:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
promptready-mcp-login --email you@x.com
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Leave out `--password` and you will be prompted for it instead — this keeps the
|
|
74
|
+
password out of your shell history.
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
Already have an access token? Set `PROMPTREADY_ACCESS_TOKEN` in the environment
|
|
79
|
+
(MCP host or shell). Environment variables take precedence over the saved
|
|
80
|
+
credentials file.
|
|
81
|
+
|
|
82
|
+
## MCP host config
|
|
83
|
+
|
|
84
|
+
### Fastest: let your AI agent install it
|
|
85
|
+
|
|
86
|
+
If you are already in an MCP-capable agent, skip the JSON editing and just
|
|
87
|
+
ask:
|
|
88
|
+
|
|
89
|
+
> Install the PromptReady MCP server for me. The PyPI package is
|
|
90
|
+
> `promptready-mcp` (stdio command `promptready-mcp`). Add it to your MCP
|
|
91
|
+
> config, then I will run the `login` tool.
|
|
92
|
+
|
|
93
|
+
In Claude Code the agent can use the built-in CLI:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
claude mcp add promptready -- promptready-mcp
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Reconnect after changing config
|
|
100
|
+
|
|
101
|
+
Hosts do not pick up MCP config changes mid-session. After changing the
|
|
102
|
+
config, restart the host or reconnect the server — in Claude Code, open the
|
|
103
|
+
`/mcp` panel and reconnect.
|
|
104
|
+
|
|
105
|
+
Seeing tools in the `/mcp` panel does **not** mean the server is connected:
|
|
106
|
+
the panel can list the tool catalog while the session has no live server,
|
|
107
|
+
and picking a tool from that list will not run it. If tools are listed but
|
|
108
|
+
calls fail, reconnect first.
|
|
109
|
+
|
|
110
|
+
### Claude Code
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
claude mcp add promptready -- promptready-mcp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The default scope is `local` (this project only). Use `--scope user` to
|
|
117
|
+
register it for all your projects, or `--scope project` to share the
|
|
118
|
+
registration through a committed `.mcp.json`.
|
|
119
|
+
|
|
120
|
+
### Cursor
|
|
121
|
+
|
|
122
|
+
Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` for a single project):
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"promptready": {
|
|
128
|
+
"command": "promptready-mcp"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Grok
|
|
135
|
+
|
|
136
|
+
```toml
|
|
137
|
+
[mcp_servers.promptready]
|
|
138
|
+
command = "promptready-mcp"
|
|
139
|
+
enabled = true
|
|
140
|
+
tool_timeout_sec = 3600
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Claude Desktop / generic JSON
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"mcpServers": {
|
|
148
|
+
"promptready": {
|
|
149
|
+
"command": "promptready-mcp"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
No access token in config files required after login.
|
|
156
|
+
|
|
157
|
+
<details>
|
|
158
|
+
<summary>Host cannot find <code>promptready-mcp</code></summary>
|
|
159
|
+
|
|
160
|
+
GUI hosts start servers with a narrow `PATH`, so a console script installed by
|
|
161
|
+
`pip install --user` is often invisible to them — the host reports a spawn
|
|
162
|
+
failure or "server disconnected" rather than a missing command.
|
|
163
|
+
|
|
164
|
+
Two reliable fixes:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{ "mcpServers": { "promptready": {
|
|
168
|
+
"command": "uvx", "args": ["promptready-mcp"] } } }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
or point at the absolute path of the script:
|
|
172
|
+
`/ABS/PATH/.venv/bin/promptready-mcp`.
|
|
173
|
+
|
|
174
|
+
</details>
|
|
175
|
+
|
|
176
|
+
## Tools
|
|
177
|
+
|
|
178
|
+
| Tool | Purpose |
|
|
179
|
+
|------|---------|
|
|
180
|
+
| `login` / `logout` | Browser auth / clear local credentials |
|
|
181
|
+
| `get_credits` | Credit balance |
|
|
182
|
+
| `get_convert_settings` / `set_convert_settings` | Saved convert defaults |
|
|
183
|
+
| `convert_pdf` | Upload path → queue (optional `wait`) |
|
|
184
|
+
| `get_status` | Job status |
|
|
185
|
+
| `wait_and_download` | Poll + save `.md` |
|
|
186
|
+
|
|
187
|
+
Downloaded names follow the web app: `{name}_PaddleOCR-VL.md` (engine label).
|
|
188
|
+
|
|
189
|
+
Credits are deducted by the server when a job is queued, exactly as on the web
|
|
190
|
+
app. `convert_pdf(wait=True)` can run for a long time, so give the host a high
|
|
191
|
+
tool timeout.
|
|
192
|
+
|
|
193
|
+
## Security
|
|
194
|
+
|
|
195
|
+
- Tokens are **never** hardcoded in this repository.
|
|
196
|
+
- Do **not** commit `~/.config/promptready/*` or `.env`.
|
|
197
|
+
- Only use the official package linked from https://promptready.space
|
|
198
|
+
- Vulnerability reports: see [SECURITY.md](SECURITY.md)
|
|
199
|
+
|
|
200
|
+
## Service terms
|
|
201
|
+
|
|
202
|
+
Using the cloud API is subject to the
|
|
203
|
+
[Terms of Service](https://promptready.space/legal/terms-of-service.en.md) and
|
|
204
|
+
[Privacy Policy](https://promptready.space/legal/privacy-policy.en.md).
|
|
205
|
+
This MIT-licensed client does not grant free unlimited conversion.
|
|
206
|
+
|
|
207
|
+
## Smoke test (no account)
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
./scripts/smoke_stdio.sh
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## License
|
|
214
|
+
|
|
215
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Thin async HTTP client for the PromptReady backend API.
|
|
2
|
+
|
|
3
|
+
Source of truth:
|
|
4
|
+
- app/api/endpoints/users.py GET /api/v1/users/credits
|
|
5
|
+
- app/api/endpoints/convert.py POST /api/v1/convert, GET /api/v1/convert/status
|
|
6
|
+
- app/api/endpoints/auth.py POST /api/v1/auth/refresh
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
API_PREFIX = "/api/v1"
|
|
16
|
+
DEFAULT_TIMEOUT = 60.0
|
|
17
|
+
CONVERT_TIMEOUT = 300.0 # upload + queue only; OCR runs async
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PromptReadyAPIError(Exception):
|
|
21
|
+
"""API or transport failure with a user-facing message."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
message: str,
|
|
26
|
+
*,
|
|
27
|
+
status_code: Optional[int] = None,
|
|
28
|
+
body: Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.message = message
|
|
32
|
+
self.status_code = status_code
|
|
33
|
+
self.body = body
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _url(base_url: str, path: str) -> str:
|
|
37
|
+
return f"{base_url.rstrip('/')}{API_PREFIX}{path}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _auth_headers(access_token: str) -> Dict[str, str]:
|
|
41
|
+
return {"Authorization": f"Bearer {access_token}"}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def fetch_credits(
|
|
45
|
+
base_url: str, access_token: str, *, timeout: float = DEFAULT_TIMEOUT
|
|
46
|
+
) -> Dict[str, Any]:
|
|
47
|
+
"""GET /users/credits → {credit_balance, email}."""
|
|
48
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
49
|
+
resp = await http.get(
|
|
50
|
+
_url(base_url, "/users/credits"),
|
|
51
|
+
headers=_auth_headers(access_token),
|
|
52
|
+
)
|
|
53
|
+
if resp.status_code >= 400:
|
|
54
|
+
raise PromptReadyAPIError(
|
|
55
|
+
f"credits failed HTTP {resp.status_code}",
|
|
56
|
+
status_code=resp.status_code,
|
|
57
|
+
body=resp.text[:500],
|
|
58
|
+
)
|
|
59
|
+
return resp.json()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def refresh_access_token(
|
|
63
|
+
base_url: str, refresh_token: str, *, timeout: float = DEFAULT_TIMEOUT
|
|
64
|
+
) -> Dict[str, str]:
|
|
65
|
+
"""POST /auth/refresh → {access_token, refresh_token}."""
|
|
66
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
67
|
+
resp = await http.post(
|
|
68
|
+
_url(base_url, "/auth/refresh"),
|
|
69
|
+
json={"refresh_token": refresh_token},
|
|
70
|
+
)
|
|
71
|
+
if resp.status_code >= 400:
|
|
72
|
+
raise PromptReadyAPIError(
|
|
73
|
+
f"token refresh failed HTTP {resp.status_code}",
|
|
74
|
+
status_code=resp.status_code,
|
|
75
|
+
body=resp.text[:500],
|
|
76
|
+
)
|
|
77
|
+
data = resp.json()
|
|
78
|
+
if not data.get("success") or not data.get("access_token"):
|
|
79
|
+
raise PromptReadyAPIError(
|
|
80
|
+
data.get("message") or "token refresh returned no access_token",
|
|
81
|
+
body=str(data)[:500],
|
|
82
|
+
)
|
|
83
|
+
out: Dict[str, str] = {"access_token": data["access_token"]}
|
|
84
|
+
if data.get("refresh_token"):
|
|
85
|
+
out["refresh_token"] = data["refresh_token"]
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def convert_pdf(
|
|
90
|
+
base_url: str,
|
|
91
|
+
access_token: str,
|
|
92
|
+
file_path: Path,
|
|
93
|
+
*,
|
|
94
|
+
timeout: float = CONVERT_TIMEOUT,
|
|
95
|
+
params: Optional[Dict[str, str]] = None,
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
"""POST /convert multipart — queues PDF and returns ConvertResponse JSON.
|
|
98
|
+
|
|
99
|
+
Query params come from user_settings (saved defaults), not per-call UI.
|
|
100
|
+
"""
|
|
101
|
+
from .user_settings import settings_to_api_params
|
|
102
|
+
|
|
103
|
+
path = Path(file_path).expanduser().resolve()
|
|
104
|
+
if not path.is_file():
|
|
105
|
+
raise PromptReadyAPIError(f"file not found: {path}")
|
|
106
|
+
if path.suffix.lower() not in (".pdf", ".csv"):
|
|
107
|
+
raise PromptReadyAPIError("only .pdf or .csv files are supported")
|
|
108
|
+
|
|
109
|
+
query = params if params is not None else settings_to_api_params()
|
|
110
|
+
data = path.read_bytes()
|
|
111
|
+
if not data:
|
|
112
|
+
raise PromptReadyAPIError("file is empty")
|
|
113
|
+
|
|
114
|
+
files = {
|
|
115
|
+
"file": (path.name, data, "application/pdf" if path.suffix.lower() == ".pdf" else "text/csv"),
|
|
116
|
+
}
|
|
117
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
118
|
+
resp = await http.post(
|
|
119
|
+
_url(base_url, "/convert"),
|
|
120
|
+
headers=_auth_headers(access_token),
|
|
121
|
+
params=query,
|
|
122
|
+
files=files,
|
|
123
|
+
)
|
|
124
|
+
if resp.status_code >= 400:
|
|
125
|
+
raise PromptReadyAPIError(
|
|
126
|
+
f"convert failed HTTP {resp.status_code}: {resp.text[:300]}",
|
|
127
|
+
status_code=resp.status_code,
|
|
128
|
+
body=resp.text[:500],
|
|
129
|
+
)
|
|
130
|
+
return resp.json()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def get_conversion_status(
|
|
134
|
+
base_url: str, access_token: str, *, timeout: float = DEFAULT_TIMEOUT
|
|
135
|
+
) -> Dict[str, Any]:
|
|
136
|
+
"""GET /convert/status for the authenticated user's session."""
|
|
137
|
+
async with httpx.AsyncClient(timeout=timeout) as http:
|
|
138
|
+
resp = await http.get(
|
|
139
|
+
_url(base_url, "/convert/status"),
|
|
140
|
+
headers=_auth_headers(access_token),
|
|
141
|
+
)
|
|
142
|
+
if resp.status_code >= 400:
|
|
143
|
+
raise PromptReadyAPIError(
|
|
144
|
+
f"status failed HTTP {resp.status_code}",
|
|
145
|
+
status_code=resp.status_code,
|
|
146
|
+
body=resp.text[:500],
|
|
147
|
+
)
|
|
148
|
+
return resp.json()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def download_url_to_path(
|
|
152
|
+
url: str, dest: Path, *, timeout: float = CONVERT_TIMEOUT
|
|
153
|
+
) -> Path:
|
|
154
|
+
"""Download markdown_url (presigned) to dest path."""
|
|
155
|
+
dest = Path(dest).expanduser().resolve()
|
|
156
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as http:
|
|
158
|
+
resp = await http.get(url)
|
|
159
|
+
if resp.status_code >= 400:
|
|
160
|
+
raise PromptReadyAPIError(
|
|
161
|
+
f"download failed HTTP {resp.status_code}",
|
|
162
|
+
status_code=resp.status_code,
|
|
163
|
+
body=resp.text[:300],
|
|
164
|
+
)
|
|
165
|
+
dest.write_bytes(resp.content)
|
|
166
|
+
return dest
|