htmlship 0.1.2__tar.gz → 0.1.4__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 (33) hide show
  1. {htmlship-0.1.2 → htmlship-0.1.4}/.gitignore +1 -0
  2. {htmlship-0.1.2 → htmlship-0.1.4}/PKG-INFO +31 -23
  3. {htmlship-0.1.2 → htmlship-0.1.4}/README.md +30 -22
  4. {htmlship-0.1.2 → htmlship-0.1.4}/pyproject.toml +1 -1
  5. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship/__init__.py +10 -10
  6. htmlship-0.1.4/src/htmlship/_version.py +1 -0
  7. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship/cli.py +35 -35
  8. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship/client.py +17 -17
  9. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship/exceptions.py +1 -1
  10. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship/models.py +14 -12
  11. htmlship-0.1.4/src/htmlship_mcp/__init__.py +1 -0
  12. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_mcp/server.py +35 -28
  13. htmlship-0.1.4/src/htmlship_server/__init__.py +1 -0
  14. htmlship-0.1.4/src/htmlship_server/db_models/__init__.py +3 -0
  15. htmlship-0.1.2/src/htmlship_server/db_models/paste.py → htmlship-0.1.4/src/htmlship_server/db_models/page.py +6 -6
  16. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/exceptions.py +1 -1
  17. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/main.py +7 -2
  18. htmlship-0.1.2/src/htmlship_server/routers/pastes.py → htmlship-0.1.4/src/htmlship_server/routers/pages.py +76 -76
  19. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/routers/view.py +24 -24
  20. htmlship-0.1.2/src/htmlship_server/schemas/pastes.py → htmlship-0.1.4/src/htmlship_server/schemas/pages.py +5 -5
  21. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/storage.py +2 -2
  22. htmlship-0.1.2/src/htmlship/_version.py +0 -1
  23. htmlship-0.1.2/src/htmlship_mcp/__init__.py +0 -1
  24. htmlship-0.1.2/src/htmlship_server/__init__.py +0 -1
  25. htmlship-0.1.2/src/htmlship_server/db_models/__init__.py +0 -3
  26. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/config.py +0 -0
  27. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/database.py +0 -0
  28. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/middleware.py +0 -0
  29. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/routers/__init__.py +0 -0
  30. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/routers/meta.py +0 -0
  31. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/schemas/__init__.py +0 -0
  32. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/security.py +0 -0
  33. {htmlship-0.1.2 → htmlship-0.1.4}/src/htmlship_server/slugs.py +0 -0
@@ -221,3 +221,4 @@ __marimo__/
221
221
  tmp/
222
222
  .htmlship/
223
223
  .DS_Store
224
+ npm/*.tgz
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: htmlship
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Host and share HTML pages from LLMs and coding agents in one line.
5
5
  Project-URL: Homepage, https://htmlship.com
6
6
  Project-URL: Repository, https://github.com/htmlship/htmlship
@@ -50,12 +50,13 @@ HTMLShip has four surfaces:
50
50
 
51
51
  - a public Python library and CLI (`htmlship` on PyPI)
52
52
  - a Node.js CLI and MCP server (`htmlship` on npm — no install required, runs via `npx`)
53
- - a FastAPI service for creating, updating, deleting, and viewing HTML pastes
53
+ - a FastAPI service for creating, updating, deleting, and viewing HTML pages
54
54
  - a stdio MCP server (the `mcp` subcommand of both CLIs) for agent clients
55
55
 
56
56
  ```bash
57
57
  # Node — runs immediately, no install
58
58
  npx htmlship publish report.html
59
+ npx htmlship publish report.html --password "demo-pass"
59
60
 
60
61
  # Python
61
62
  pip install htmlship
@@ -64,15 +65,20 @@ pip install htmlship
64
65
  ```python
65
66
  import htmlship
66
67
 
67
- paste = htmlship.publish("<h1>Hello</h1>", title="Demo", expires_in=60) # minutes
68
- print(paste.url)
69
- print(paste.owner_key) # save this to update or delete the paste later
68
+ page = htmlship.publish(
69
+ "<h1>Hello</h1>",
70
+ title="Demo",
71
+ password="demo-pass",
72
+ expires_in=60, # minutes
73
+ )
74
+ print(page.url)
75
+ print(page.owner_key) # save this to update or delete the page later
70
76
  ```
71
77
 
72
78
  ```bash
73
- curl -X POST https://api.htmlship.com/api/v1/pastes \
79
+ curl -X POST https://api.htmlship.com/api/v1/pages \
74
80
  -H "Content-Type: application/json" \
75
- -d '{"html":"<h1>Hello</h1>","title":"Demo"}'
81
+ -d '{"html":"<h1>Hello</h1>","title":"Demo","password":"demo-pass"}'
76
82
  ```
77
83
 
78
84
  See [`htmlship-implementation-spec.md`](./htmlship-implementation-spec.md) for the product spec and [`DEPLOY.md`](./DEPLOY.md) for the production runbook.
@@ -84,27 +90,28 @@ The module-level helpers use `https://api.htmlship.com` by default. Override wit
84
90
  ```python
85
91
  import htmlship
86
92
 
87
- paste = htmlship.publish(
93
+ page = htmlship.publish(
88
94
  "<h1>Hello</h1>",
89
95
  title="Demo",
90
96
  password="optional-password",
91
97
  expires_in=1440, # minutes (24 hours)
92
98
  )
93
99
 
94
- fresh = htmlship.get(paste.slug)
95
- updated = htmlship.update(paste.slug, "<h1>Updated</h1>", owner_key=paste.owner_key)
96
- htmlship.delete(updated.slug, owner_key=paste.owner_key)
100
+ fresh = htmlship.get(page.slug)
101
+ updated = htmlship.update(page.slug, "<h1>Updated</h1>", owner_key=page.owner_key)
102
+ htmlship.delete(updated.slug, owner_key=page.owner_key)
97
103
  ```
98
104
 
99
- `owner_key` is returned only when a paste is created. It is required for updates and deletes, and the API does not return it again from metadata calls.
105
+ `owner_key` is returned only when a page is created. It is the publisher-only secret required for updates and deletes, and the API does not return it again from metadata calls. `password` is only a view-time gate for readers; it does not authorize mutations.
100
106
 
101
107
  ## CLI
102
108
 
103
- The CLI is shipped both as a Python package (`pip install htmlship`) and an npm package (`npx htmlship` or `npm i -g htmlship`). The two share the same on-disk owner-key store, so a paste created in one is editable from the other.
109
+ The CLI is shipped both as a Python package (`pip install htmlship`) and an npm package (`npx htmlship` or `npm i -g htmlship`). The two share the same on-disk owner-key store, so a page created in one is editable from the other.
104
110
 
105
111
  ```bash
106
112
  htmlship publish report.html
107
113
  cat report.html | htmlship publish -
114
+ htmlship publish report.html --password "demo-pass"
108
115
  htmlship publish --file report.html --title "Q4 Report" --expires-in 60
109
116
 
110
117
  htmlship get <slug>
@@ -117,10 +124,11 @@ Equivalent npx form (no install):
117
124
 
118
125
  ```bash
119
126
  npx htmlship publish report.html
127
+ npx htmlship publish report.html --password "demo-pass"
120
128
  npx htmlship list-mine
121
129
  ```
122
130
 
123
- The CLI stores owner keys in `~/.htmlship/keys.json` so `update`, `delete`, and `list-mine` can work with pastes you created locally. Set `HTMLSHIP_KEYS_DIR` to use another key-store directory, and set `HTMLSHIP_API_URL` to point the CLI at a local or staging API.
131
+ The CLI stores owner keys in `~/.htmlship/keys.json` so `update`, `delete`, and `list-mine` can work with pages you created locally. Set `HTMLSHIP_KEYS_DIR` to use another key-store directory, and set `HTMLSHIP_API_URL` to point the CLI at a local or staging API.
124
132
 
125
133
  ## API
126
134
 
@@ -130,11 +138,11 @@ Base URL: `https://api.htmlship.com`.
130
138
  | --- | --- | --- |
131
139
  | `GET` | `/health` | Health check with service version. |
132
140
  | `GET` | `/version` | Service version. |
133
- | `POST` | `/api/v1/pastes` | Create a paste. |
134
- | `GET` | `/api/v1/pastes/{slug}` | Fetch paste metadata. |
135
- | `PATCH` | `/api/v1/pastes/{slug}` | Replace HTML or title. Requires `X-Owner-Key`. |
136
- | `DELETE` | `/api/v1/pastes/{slug}` | Soft-delete a paste. Requires `X-Owner-Key`. |
137
- | `POST` | `/api/v1/pastes/{slug}/version` | Create a new paste linked to an existing parent slug. |
141
+ | `POST` | `/api/v1/pages` | Create a page. |
142
+ | `GET` | `/api/v1/pages/{slug}` | Fetch page metadata. |
143
+ | `PATCH` | `/api/v1/pages/{slug}` | Replace HTML or title. Requires `X-Owner-Key`. |
144
+ | `DELETE` | `/api/v1/pages/{slug}` | Soft-delete a page. Requires `X-Owner-Key`. |
145
+ | `POST` | `/api/v1/pages/{slug}/version` | Create a new page linked to an existing parent slug. |
138
146
 
139
147
  Create payload:
140
148
 
@@ -185,7 +193,7 @@ curl "http://localhost:8000/<slug>?_host=view.htmlship.com"
185
193
 
186
194
  HTMLShip ships a stdio MCP server with three tools:
187
195
 
188
- - `publish_html`
196
+ - `publish_html` (accepts optional `password`)
189
197
  - `fetch_html`
190
198
  - `update_html`
191
199
 
@@ -278,7 +286,7 @@ The server reads `.env` via Pydantic settings.
278
286
  | `DATABASE_URL` | `postgresql+asyncpg://htmlship:htmlship@localhost:5433/htmlship` | Async SQLAlchemy database URL. |
279
287
  | `PUBLIC_BASE_DOMAIN` | `htmlship.com` | Base domain used to derive host routing. |
280
288
  | `API_BASE_URL` | `https://api.htmlship.com` | Public API URL setting. |
281
- | `VIEW_BASE_URL` | `https://view.htmlship.com` | Public view URL used in paste responses. |
289
+ | `VIEW_BASE_URL` | `https://view.htmlship.com` | Public view URL used in page responses. |
282
290
  | `LANDING_BASE_URL` | `https://htmlship.com` | Public landing URL. |
283
291
  | `SPACES_BUCKET` | empty | If empty, use local blob storage; otherwise use DigitalOcean Spaces/S3. |
284
292
  | `SPACES_REGION` | `nyc3` | Spaces/S3 region. |
@@ -288,13 +296,13 @@ The server reads `.env` via Pydantic settings.
288
296
  | `ENVIRONMENT` | `development` | Enables API docs outside production and secure cookies in production. |
289
297
  | `LOG_LEVEL` | `info` | Application log level. |
290
298
  | `MAX_PAYLOAD_BYTES` | `10485760` | Server-side HTML size limit. |
291
- | `DEFAULT_EXPIRES_IN_MINUTES` | empty | Optional default TTL (minutes) for new pastes. |
299
+ | `DEFAULT_EXPIRES_IN_MINUTES` | empty | Optional default TTL (minutes) for new pages. |
292
300
 
293
301
  ## Architecture
294
302
 
295
303
  One FastAPI process hosts the landing page, JSON API, and view renderer. `HostRoutingMiddleware` classifies requests by host and prevents API routes from being served on the view host.
296
304
 
297
- Postgres stores paste metadata, owner-key/password hashes, expiry, view counts, and parent-version links. HTML bodies are stored as blobs, either in `LocalBlobStore` for development/tests or DigitalOcean Spaces in production.
305
+ Postgres stores page metadata, owner-key/password hashes, expiry, view counts, and parent-version links. HTML bodies are stored as blobs, either in `LocalBlobStore` for development/tests or DigitalOcean Spaces in production.
298
306
 
299
307
  ## Project Layout
300
308
 
@@ -6,12 +6,13 @@ HTMLShip has four surfaces:
6
6
 
7
7
  - a public Python library and CLI (`htmlship` on PyPI)
8
8
  - a Node.js CLI and MCP server (`htmlship` on npm — no install required, runs via `npx`)
9
- - a FastAPI service for creating, updating, deleting, and viewing HTML pastes
9
+ - a FastAPI service for creating, updating, deleting, and viewing HTML pages
10
10
  - a stdio MCP server (the `mcp` subcommand of both CLIs) for agent clients
11
11
 
12
12
  ```bash
13
13
  # Node — runs immediately, no install
14
14
  npx htmlship publish report.html
15
+ npx htmlship publish report.html --password "demo-pass"
15
16
 
16
17
  # Python
17
18
  pip install htmlship
@@ -20,15 +21,20 @@ pip install htmlship
20
21
  ```python
21
22
  import htmlship
22
23
 
23
- paste = htmlship.publish("<h1>Hello</h1>", title="Demo", expires_in=60) # minutes
24
- print(paste.url)
25
- print(paste.owner_key) # save this to update or delete the paste later
24
+ page = htmlship.publish(
25
+ "<h1>Hello</h1>",
26
+ title="Demo",
27
+ password="demo-pass",
28
+ expires_in=60, # minutes
29
+ )
30
+ print(page.url)
31
+ print(page.owner_key) # save this to update or delete the page later
26
32
  ```
27
33
 
28
34
  ```bash
29
- curl -X POST https://api.htmlship.com/api/v1/pastes \
35
+ curl -X POST https://api.htmlship.com/api/v1/pages \
30
36
  -H "Content-Type: application/json" \
31
- -d '{"html":"<h1>Hello</h1>","title":"Demo"}'
37
+ -d '{"html":"<h1>Hello</h1>","title":"Demo","password":"demo-pass"}'
32
38
  ```
33
39
 
34
40
  See [`htmlship-implementation-spec.md`](./htmlship-implementation-spec.md) for the product spec and [`DEPLOY.md`](./DEPLOY.md) for the production runbook.
@@ -40,27 +46,28 @@ The module-level helpers use `https://api.htmlship.com` by default. Override wit
40
46
  ```python
41
47
  import htmlship
42
48
 
43
- paste = htmlship.publish(
49
+ page = htmlship.publish(
44
50
  "<h1>Hello</h1>",
45
51
  title="Demo",
46
52
  password="optional-password",
47
53
  expires_in=1440, # minutes (24 hours)
48
54
  )
49
55
 
50
- fresh = htmlship.get(paste.slug)
51
- updated = htmlship.update(paste.slug, "<h1>Updated</h1>", owner_key=paste.owner_key)
52
- htmlship.delete(updated.slug, owner_key=paste.owner_key)
56
+ fresh = htmlship.get(page.slug)
57
+ updated = htmlship.update(page.slug, "<h1>Updated</h1>", owner_key=page.owner_key)
58
+ htmlship.delete(updated.slug, owner_key=page.owner_key)
53
59
  ```
54
60
 
55
- `owner_key` is returned only when a paste is created. It is required for updates and deletes, and the API does not return it again from metadata calls.
61
+ `owner_key` is returned only when a page is created. It is the publisher-only secret required for updates and deletes, and the API does not return it again from metadata calls. `password` is only a view-time gate for readers; it does not authorize mutations.
56
62
 
57
63
  ## CLI
58
64
 
59
- The CLI is shipped both as a Python package (`pip install htmlship`) and an npm package (`npx htmlship` or `npm i -g htmlship`). The two share the same on-disk owner-key store, so a paste created in one is editable from the other.
65
+ The CLI is shipped both as a Python package (`pip install htmlship`) and an npm package (`npx htmlship` or `npm i -g htmlship`). The two share the same on-disk owner-key store, so a page created in one is editable from the other.
60
66
 
61
67
  ```bash
62
68
  htmlship publish report.html
63
69
  cat report.html | htmlship publish -
70
+ htmlship publish report.html --password "demo-pass"
64
71
  htmlship publish --file report.html --title "Q4 Report" --expires-in 60
65
72
 
66
73
  htmlship get <slug>
@@ -73,10 +80,11 @@ Equivalent npx form (no install):
73
80
 
74
81
  ```bash
75
82
  npx htmlship publish report.html
83
+ npx htmlship publish report.html --password "demo-pass"
76
84
  npx htmlship list-mine
77
85
  ```
78
86
 
79
- The CLI stores owner keys in `~/.htmlship/keys.json` so `update`, `delete`, and `list-mine` can work with pastes you created locally. Set `HTMLSHIP_KEYS_DIR` to use another key-store directory, and set `HTMLSHIP_API_URL` to point the CLI at a local or staging API.
87
+ The CLI stores owner keys in `~/.htmlship/keys.json` so `update`, `delete`, and `list-mine` can work with pages you created locally. Set `HTMLSHIP_KEYS_DIR` to use another key-store directory, and set `HTMLSHIP_API_URL` to point the CLI at a local or staging API.
80
88
 
81
89
  ## API
82
90
 
@@ -86,11 +94,11 @@ Base URL: `https://api.htmlship.com`.
86
94
  | --- | --- | --- |
87
95
  | `GET` | `/health` | Health check with service version. |
88
96
  | `GET` | `/version` | Service version. |
89
- | `POST` | `/api/v1/pastes` | Create a paste. |
90
- | `GET` | `/api/v1/pastes/{slug}` | Fetch paste metadata. |
91
- | `PATCH` | `/api/v1/pastes/{slug}` | Replace HTML or title. Requires `X-Owner-Key`. |
92
- | `DELETE` | `/api/v1/pastes/{slug}` | Soft-delete a paste. Requires `X-Owner-Key`. |
93
- | `POST` | `/api/v1/pastes/{slug}/version` | Create a new paste linked to an existing parent slug. |
97
+ | `POST` | `/api/v1/pages` | Create a page. |
98
+ | `GET` | `/api/v1/pages/{slug}` | Fetch page metadata. |
99
+ | `PATCH` | `/api/v1/pages/{slug}` | Replace HTML or title. Requires `X-Owner-Key`. |
100
+ | `DELETE` | `/api/v1/pages/{slug}` | Soft-delete a page. Requires `X-Owner-Key`. |
101
+ | `POST` | `/api/v1/pages/{slug}/version` | Create a new page linked to an existing parent slug. |
94
102
 
95
103
  Create payload:
96
104
 
@@ -141,7 +149,7 @@ curl "http://localhost:8000/<slug>?_host=view.htmlship.com"
141
149
 
142
150
  HTMLShip ships a stdio MCP server with three tools:
143
151
 
144
- - `publish_html`
152
+ - `publish_html` (accepts optional `password`)
145
153
  - `fetch_html`
146
154
  - `update_html`
147
155
 
@@ -234,7 +242,7 @@ The server reads `.env` via Pydantic settings.
234
242
  | `DATABASE_URL` | `postgresql+asyncpg://htmlship:htmlship@localhost:5433/htmlship` | Async SQLAlchemy database URL. |
235
243
  | `PUBLIC_BASE_DOMAIN` | `htmlship.com` | Base domain used to derive host routing. |
236
244
  | `API_BASE_URL` | `https://api.htmlship.com` | Public API URL setting. |
237
- | `VIEW_BASE_URL` | `https://view.htmlship.com` | Public view URL used in paste responses. |
245
+ | `VIEW_BASE_URL` | `https://view.htmlship.com` | Public view URL used in page responses. |
238
246
  | `LANDING_BASE_URL` | `https://htmlship.com` | Public landing URL. |
239
247
  | `SPACES_BUCKET` | empty | If empty, use local blob storage; otherwise use DigitalOcean Spaces/S3. |
240
248
  | `SPACES_REGION` | `nyc3` | Spaces/S3 region. |
@@ -244,13 +252,13 @@ The server reads `.env` via Pydantic settings.
244
252
  | `ENVIRONMENT` | `development` | Enables API docs outside production and secure cookies in production. |
245
253
  | `LOG_LEVEL` | `info` | Application log level. |
246
254
  | `MAX_PAYLOAD_BYTES` | `10485760` | Server-side HTML size limit. |
247
- | `DEFAULT_EXPIRES_IN_MINUTES` | empty | Optional default TTL (minutes) for new pastes. |
255
+ | `DEFAULT_EXPIRES_IN_MINUTES` | empty | Optional default TTL (minutes) for new pages. |
248
256
 
249
257
  ## Architecture
250
258
 
251
259
  One FastAPI process hosts the landing page, JSON API, and view renderer. `HostRoutingMiddleware` classifies requests by host and prevents API routes from being served on the view host.
252
260
 
253
- Postgres stores paste metadata, owner-key/password hashes, expiry, view counts, and parent-version links. HTML bodies are stored as blobs, either in `LocalBlobStore` for development/tests or DigitalOcean Spaces in production.
261
+ Postgres stores page metadata, owner-key/password hashes, expiry, view counts, and parent-version links. HTML bodies are stored as blobs, either in `LocalBlobStore` for development/tests or DigitalOcean Spaces in production.
254
262
 
255
263
  ## Project Layout
256
264
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "htmlship"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "Host and share HTML pages from LLMs and coding agents in one line."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -3,12 +3,12 @@
3
3
  Quick start:
4
4
 
5
5
  import htmlship
6
- paste = htmlship.publish("<h1>Hello</h1>", expires_in=60) # minutes
7
- print(paste.url)
8
- print(paste.owner_key)
6
+ page = htmlship.publish("<h1>Hello</h1>", expires_in=60) # minutes
7
+ print(page.url)
8
+ print(page.owner_key)
9
9
 
10
- paste.update("<h1>Hello, again</h1>")
11
- paste.delete()
10
+ page.update("<h1>Hello, again</h1>")
11
+ page.delete()
12
12
 
13
13
  Configuration via environment:
14
14
  HTMLSHIP_API_URL (default: https://api.htmlship.com)
@@ -25,7 +25,7 @@ from .exceptions import (
25
25
  NotFoundError,
26
26
  RateLimitError,
27
27
  )
28
- from .models import Paste
28
+ from .models import Page
29
29
 
30
30
  _default_client: HTMLShipClient | None = None
31
31
 
@@ -61,7 +61,7 @@ def publish(
61
61
  expires_in: int | None = None,
62
62
  parent_slug: str | None = None,
63
63
  sandbox_mode: str = "strict",
64
- ) -> Paste:
64
+ ) -> Page:
65
65
  """Publish HTML. ``expires_in`` is in minutes (max 10080 = 7 days)."""
66
66
  return _get_default_client().publish(
67
67
  html,
@@ -73,11 +73,11 @@ def publish(
73
73
  )
74
74
 
75
75
 
76
- def get(slug: str) -> Paste:
76
+ def get(slug: str) -> Page:
77
77
  return _get_default_client().get(slug)
78
78
 
79
79
 
80
- def update(slug: str, html: str, *, owner_key: str, title: str | None = None) -> Paste:
80
+ def update(slug: str, html: str, *, owner_key: str, title: str | None = None) -> Page:
81
81
  return _get_default_client().update(slug, html, owner_key=owner_key, title=title)
82
82
 
83
83
 
@@ -88,7 +88,7 @@ def delete(slug: str, *, owner_key: str) -> None:
88
88
  __all__ = [
89
89
  "__version__",
90
90
  "HTMLShipClient",
91
- "Paste",
91
+ "Page",
92
92
  "HTMLShipError",
93
93
  "NotFoundError",
94
94
  "AuthError",
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -111,7 +111,7 @@ def main(ctx: click.Context, api_url: str | None) -> None:
111
111
  @click.argument("source", required=False)
112
112
  @click.option("--file", "-f", "file_", type=click.Path(exists=True, dir_okay=False), help="HTML file path.")
113
113
  @click.option("--title", default=None, help="Optional title.")
114
- @click.option("--password", default=None, help="Password-protect the paste.")
114
+ @click.option("--password", default=None, help="Password-protect the page.")
115
115
  @click.option(
116
116
  "--expires-in",
117
117
  type=int,
@@ -142,7 +142,7 @@ def publish(
142
142
  html = _read_html(source, file_)
143
143
  client = _make_client(ctx.obj["api_url"])
144
144
  try:
145
- paste = client.publish(
145
+ page = client.publish(
146
146
  html,
147
147
  title=title,
148
148
  password=password,
@@ -153,18 +153,18 @@ def publish(
153
153
  finally:
154
154
  client.close()
155
155
 
156
- _remember(paste.slug, paste.owner_key or "", paste.url, title)
156
+ _remember(page.slug, page.owner_key or "", page.url, title)
157
157
 
158
158
  if quiet:
159
- click.echo(paste.url)
159
+ click.echo(page.url)
160
160
  return
161
161
 
162
- click.echo(paste.url)
163
- click.echo(f"slug: {paste.slug}", err=True)
164
- click.echo(f"owner_key: {paste.owner_key} (saved to {KEYS_FILE})", err=True)
165
- if paste.expires_at:
166
- click.echo(f"expires: {paste.expires_at.isoformat()}", err=True)
167
- if not no_clipboard and _try_clipboard(paste.url):
162
+ click.echo(page.url)
163
+ click.echo(f"slug: {page.slug}", err=True)
164
+ click.echo(f"owner_key: {page.owner_key} (saved to {KEYS_FILE})", err=True)
165
+ if page.expires_at:
166
+ click.echo(f"expires: {page.expires_at.isoformat()}", err=True)
167
+ if not no_clipboard and _try_clipboard(page.url):
168
168
  click.echo("(URL copied to clipboard)", err=True)
169
169
 
170
170
 
@@ -172,12 +172,12 @@ def publish(
172
172
  @click.argument("slug")
173
173
  @click.pass_context
174
174
  def get(ctx: click.Context, slug: str) -> None:
175
- """Show metadata for a paste."""
175
+ """Show metadata for a page."""
176
176
  client = _make_client(ctx.obj["api_url"])
177
177
  try:
178
- paste = client.get(slug)
178
+ page = client.get(slug)
179
179
  except NotFoundError as exc:
180
- raise click.ClickException(f"paste '{slug}' not found") from exc
180
+ raise click.ClickException(f"page '{slug}' not found") from exc
181
181
  except HTMLShipError as exc:
182
182
  raise click.ClickException(str(exc)) from exc
183
183
  finally:
@@ -185,16 +185,16 @@ def get(ctx: click.Context, slug: str) -> None:
185
185
 
186
186
  click.echo(json.dumps(
187
187
  {
188
- "slug": paste.slug,
189
- "url": paste.url,
190
- "title": paste.title,
191
- "view_count": paste.view_count,
192
- "size_bytes": paste.size_bytes,
193
- "has_password": paste.has_password,
194
- "parent_slug": paste.parent_slug,
195
- "expires_at": paste.expires_at.isoformat() if paste.expires_at else None,
196
- "created_at": paste.created_at.isoformat() if paste.created_at else None,
197
- "updated_at": paste.updated_at.isoformat() if paste.updated_at else None,
188
+ "slug": page.slug,
189
+ "url": page.url,
190
+ "title": page.title,
191
+ "view_count": page.view_count,
192
+ "size_bytes": page.size_bytes,
193
+ "has_password": page.has_password,
194
+ "parent_slug": page.parent_slug,
195
+ "expires_at": page.expires_at.isoformat() if page.expires_at else None,
196
+ "created_at": page.created_at.isoformat() if page.created_at else None,
197
+ "updated_at": page.updated_at.isoformat() if page.updated_at else None,
198
198
  },
199
199
  indent=2,
200
200
  ))
@@ -215,7 +215,7 @@ def update(
215
215
  title: str | None,
216
216
  owner_key: str | None,
217
217
  ) -> None:
218
- """Replace HTML for an existing paste."""
218
+ """Replace HTML for an existing page."""
219
219
  html = _read_html(source, file_)
220
220
  key = owner_key or _lookup_owner_key(slug)
221
221
  if not key:
@@ -224,13 +224,13 @@ def update(
224
224
  )
225
225
  client = _make_client(ctx.obj["api_url"])
226
226
  try:
227
- paste = client.update(slug, html, owner_key=key, title=title)
227
+ page = client.update(slug, html, owner_key=key, title=title)
228
228
  except HTMLShipError as exc:
229
229
  raise click.ClickException(str(exc)) from exc
230
230
  finally:
231
231
  client.close()
232
232
 
233
- click.echo(paste.url)
233
+ click.echo(page.url)
234
234
 
235
235
 
236
236
  @main.command()
@@ -239,14 +239,14 @@ def update(
239
239
  @click.option("--yes", "-y", is_flag=True, help="Skip confirmation.")
240
240
  @click.pass_context
241
241
  def delete(ctx: click.Context, slug: str, owner_key: str | None, yes: bool) -> None:
242
- """Soft-delete a paste."""
242
+ """Soft-delete a page."""
243
243
  key = owner_key or _lookup_owner_key(slug)
244
244
  if not key:
245
245
  raise click.ClickException(
246
246
  f"no owner key for '{slug}' in {KEYS_FILE}; pass --owner-key explicitly"
247
247
  )
248
248
  if not yes:
249
- click.confirm(f"Delete paste '{slug}'?", abort=True)
249
+ click.confirm(f"Delete page '{slug}'?", abort=True)
250
250
  client = _make_client(ctx.obj["api_url"])
251
251
  try:
252
252
  client.delete(slug, owner_key=key)
@@ -262,10 +262,10 @@ def delete(ctx: click.Context, slug: str, owner_key: str | None, yes: bool) -> N
262
262
  @click.option("--limit", type=int, default=20, show_default=True)
263
263
  @click.pass_context
264
264
  def list_mine(ctx: click.Context, limit: int) -> None:
265
- """List pastes whose owner keys are saved locally."""
265
+ """List pages whose owner keys are saved locally."""
266
266
  keys = _load_keys()
267
267
  if not keys:
268
- click.echo(f"No saved pastes in {KEYS_FILE}.")
268
+ click.echo(f"No saved pages in {KEYS_FILE}.")
269
269
  return
270
270
  items = sorted(keys.items(), key=lambda kv: kv[1].get("saved_at", ""), reverse=True)[:limit]
271
271
 
@@ -274,13 +274,13 @@ def list_mine(ctx: click.Context, limit: int) -> None:
274
274
  rows = []
275
275
  for slug, info in items:
276
276
  try:
277
- paste = client.get(slug)
278
- size = f"{paste.size_bytes}B"
279
- views = paste.view_count
280
- title = paste.title or info.get("title") or ""
277
+ page = client.get(slug)
278
+ size = f"{page.size_bytes}B"
279
+ views = page.view_count
280
+ title = page.title or info.get("title") or ""
281
281
  status = "ok"
282
282
  except NotFoundError:
283
- paste = None
283
+ page = None
284
284
  size = "—"
285
285
  views = "—"
286
286
  title = info.get("title") or ""
@@ -8,7 +8,7 @@ import httpx
8
8
 
9
9
  from ._version import __version__
10
10
  from .exceptions import APIError, AuthError, HTMLShipError, NotFoundError, RateLimitError
11
- from .models import Paste
11
+ from .models import Page
12
12
 
13
13
  DEFAULT_API_URL = "https://api.htmlship.com"
14
14
  DEFAULT_TIMEOUT = 30.0
@@ -65,10 +65,10 @@ class HTMLShipClient:
65
65
  expires_in: int | None = None,
66
66
  parent_slug: str | None = None,
67
67
  sandbox_mode: str = "strict",
68
- ) -> Paste:
69
- """Create a paste. ``expires_in`` is in minutes (max 10080 = 7 days).
68
+ ) -> Page:
69
+ """Create a page. ``expires_in`` is in minutes (max 10080 = 7 days).
70
70
 
71
- Returns a Paste with owner_key set.
71
+ Returns a Page with owner_key set.
72
72
  """
73
73
  body: dict[str, Any] = {"html": html, "sandbox_mode": sandbox_mode}
74
74
  if title is not None:
@@ -79,9 +79,9 @@ class HTMLShipClient:
79
79
  body["expires_in"] = expires_in
80
80
  if parent_slug is not None:
81
81
  body["parent_slug"] = parent_slug
82
- r = self._http.post("/api/v1/pastes", json=body)
82
+ r = self._http.post("/api/v1/pages", json=body)
83
83
  data = self._handle(r)
84
- return Paste(
84
+ return Page(
85
85
  slug=data["slug"],
86
86
  url=data["url"],
87
87
  owner_key=data["owner_key"],
@@ -90,11 +90,11 @@ class HTMLShipClient:
90
90
  _client=self,
91
91
  )
92
92
 
93
- def get(self, slug: str) -> Paste:
94
- """Fetch metadata for a paste. owner_key is None on the returned object."""
95
- r = self._http.get(f"/api/v1/pastes/{slug}")
93
+ def get(self, slug: str) -> Page:
94
+ """Fetch metadata for a page. owner_key is None on the returned object."""
95
+ r = self._http.get(f"/api/v1/pages/{slug}")
96
96
  data = self._handle(r)
97
- return Paste(
97
+ return Page(
98
98
  slug=data["slug"],
99
99
  url=data["url"],
100
100
  owner_key=None,
@@ -116,17 +116,17 @@ class HTMLShipClient:
116
116
  *,
117
117
  owner_key: str,
118
118
  title: str | None = None,
119
- ) -> Paste:
119
+ ) -> Page:
120
120
  body: dict[str, Any] = {"html": html}
121
121
  if title is not None:
122
122
  body["title"] = title
123
123
  r = self._http.patch(
124
- f"/api/v1/pastes/{slug}",
124
+ f"/api/v1/pages/{slug}",
125
125
  json=body,
126
126
  headers={"X-Owner-Key": owner_key},
127
127
  )
128
128
  data = self._handle(r)
129
- return Paste(
129
+ return Page(
130
130
  slug=data["slug"],
131
131
  url=data["url"],
132
132
  owner_key=owner_key,
@@ -137,7 +137,7 @@ class HTMLShipClient:
137
137
 
138
138
  def delete(self, slug: str, *, owner_key: str) -> None:
139
139
  r = self._http.delete(
140
- f"/api/v1/pastes/{slug}",
140
+ f"/api/v1/pages/{slug}",
141
141
  headers={"X-Owner-Key": owner_key},
142
142
  )
143
143
  self._handle(r, expect_no_body=True)
@@ -150,7 +150,7 @@ class HTMLShipClient:
150
150
  title: str | None = None,
151
151
  password: str | None = None,
152
152
  expires_in: int | None = None,
153
- ) -> Paste:
153
+ ) -> Page:
154
154
  body: dict[str, Any] = {"html": html, "sandbox_mode": "strict"}
155
155
  if title is not None:
156
156
  body["title"] = title
@@ -158,9 +158,9 @@ class HTMLShipClient:
158
158
  body["password"] = password
159
159
  if expires_in is not None:
160
160
  body["expires_in"] = expires_in
161
- r = self._http.post(f"/api/v1/pastes/{parent_slug}/version", json=body)
161
+ r = self._http.post(f"/api/v1/pages/{parent_slug}/version", json=body)
162
162
  data = self._handle(r)
163
- return Paste(
163
+ return Page(
164
164
  slug=data["slug"],
165
165
  url=data["url"],
166
166
  owner_key=data["owner_key"],
@@ -6,7 +6,7 @@ class HTMLShipError(Exception):
6
6
 
7
7
 
8
8
  class NotFoundError(HTMLShipError):
9
- """Returned when a paste does not exist or has expired."""
9
+ """Returned when a page does not exist or has expired."""
10
10
 
11
11
 
12
12
  class AuthError(HTMLShipError):