Skip to content

Commit 643381d

Browse files
committed
docs: update documentation links and image replacement safeguards
1 parent d041b7e commit 643381d

5 files changed

Lines changed: 235 additions & 222 deletions

File tree

‎docs/cli.md‎

Lines changed: 113 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,113 @@
1-
---
2-
layout: default
3-
title: Unified CLI
4-
---
5-
6-
# Unified CLI
7-
8-
Global options such as `--json`, `--cookies`, and `--publication-url` must
9-
appear before the command.
10-
11-
## Create an unpublished draft
12-
13-
```bash
14-
substack drafts create post.md
15-
```
16-
17-
The title comes from the first Markdown heading, then the filename if the file
18-
has no heading. Use `--title` to override it.
19-
20-
## Export a draft to Markdown
21-
22-
Print a read-only Markdown backup to standard output:
23-
24-
```bash
25-
substack drafts export 12345
26-
```
27-
28-
Write it to a UTF-8 file:
29-
30-
```bash
31-
substack drafts export 12345 --output backup.md
32-
```
33-
34-
The command refuses to replace an existing file unless `--force` is present.
35-
It fetches the draft but performs no server writes. Content without a supported
36-
Markdown representation is emitted as a versioned opaque HTML comment instead
37-
of being silently discarded.
38-
39-
## Update a draft from Markdown
40-
41-
Preview the generated update without writing to Substack:
42-
43-
```bash
44-
substack drafts update 12345 revised-post.md --dry-run --yes
45-
```
46-
47-
Apply it after review:
48-
49-
```bash
50-
substack drafts update 12345 revised-post.md --yes
51-
```
52-
53-
Updates preserve unsupported editor nodes exported as
54-
`python-substack-node:v1` markers. The command refuses to remove or alter
55-
those nodes by default. `--allow-unsupported-change` is available only with
56-
`--yes` for an intentional destructive change. The CLI uses its authenticated
57-
API client while rendering, so local images can upload even during a dry run.
58-
59-
## Select a publication
60-
61-
List every publication available to the authenticated account:
62-
63-
```bash
64-
substack publications list
65-
```
66-
67-
Select one for a single command:
68-
69-
```bash
70-
substack --publication-url https://example.substack.com drafts list
71-
```
72-
73-
## Schedule an existing draft
74-
75-
Pass an ISO 8601 timestamp with a timezone offset or `Z`:
76-
77-
```bash
78-
substack drafts schedule 12345 --at 2030-01-02T09:00:00+01:00
79-
```
80-
81-
Remove the schedule without deleting the draft:
82-
83-
```bash
84-
substack drafts unschedule 12345
85-
```
86-
87-
## Stable JSON for automation
88-
89-
```bash
90-
substack --json drafts list --limit 10
91-
substack --json drafts export 12345
92-
```
93-
94-
The command writes a JSON object containing `drafts`, `count`, `filter`,
95-
`offset`, and `limit`. Errors are also JSON when `--json` is present.
96-
Export JSON contains `action`, `draft_id`, `markdown`, and
97-
`unsupported_nodes`.
98-
99-
## Publish and delete
100-
101-
```bash
102-
substack drafts publish 12345 --no-send
103-
substack drafts delete 12345 --yes
104-
```
105-
106-
Publishing and deletion require confirmation. JSON and noninteractive
107-
workflows must pass `--yes`. Publishing runs Substack's prepublish check first.
108-
See [safety and publishing behavior](safety.md).
109-
110-
The original console scripts remain supported. See
111-
[legacy CLI commands](legacy-cli.md).
1+
---
2+
layout: default
3+
title: Unified CLI
4+
---
5+
6+
# Unified CLI
7+
8+
Global options such as `--json`, `--cookies`, and `--publication-url` must
9+
appear before the command.
10+
11+
## Create an unpublished draft
12+
13+
```bash
14+
substack drafts create post.md
15+
```
16+
17+
The title comes from the first Markdown heading, then the filename if the file
18+
has no heading. Use `--title` to override it.
19+
20+
## Export a draft to Markdown
21+
22+
Print a read-only Markdown backup to standard output:
23+
24+
```bash
25+
substack drafts export 12345
26+
```
27+
28+
Write it to a UTF-8 file:
29+
30+
```bash
31+
substack drafts export 12345 --output backup.md
32+
```
33+
34+
The command refuses to replace an existing file unless `--force` is present.
35+
It fetches the draft but performs no server writes. Content without a supported
36+
Markdown representation is emitted as a versioned opaque HTML comment instead
37+
of being silently discarded.
38+
39+
## Update a draft from Markdown
40+
41+
Preview the generated update without writing to Substack:
42+
43+
```bash
44+
substack drafts update 12345 revised-post.md --dry-run --yes
45+
```
46+
47+
Apply it after review:
48+
49+
```bash
50+
substack drafts update 12345 revised-post.md --yes
51+
```
52+
53+
Updates preserve unsupported editor nodes exported as
54+
`python-substack-node:v1` markers and editor image attributes preserved in
55+
`python-substack-image:v2` markers. The command refuses to remove or alter
56+
those nodes by default. `--allow-unsupported-change` and
57+
`--allow-image-replacement` are available only with `--yes` for intentional
58+
modifications or unmatched image replacements. The CLI uses its authenticated
59+
API client while rendering, so local images can upload even during a dry run.
60+
61+
## Select a publication
62+
63+
List every publication available to the authenticated account:
64+
65+
```bash
66+
substack publications list
67+
```
68+
69+
Select one for a single command:
70+
71+
```bash
72+
substack --publication-url https://example.substack.com drafts list
73+
```
74+
75+
## Schedule an existing draft
76+
77+
Pass an ISO 8601 timestamp with a timezone offset or `Z`:
78+
79+
```bash
80+
substack drafts schedule 12345 --at 2030-01-02T09:00:00+01:00
81+
```
82+
83+
Remove the schedule without deleting the draft:
84+
85+
```bash
86+
substack drafts unschedule 12345
87+
```
88+
89+
## Stable JSON for automation
90+
91+
```bash
92+
substack --json drafts list --limit 10
93+
substack --json drafts export 12345
94+
```
95+
96+
The command writes a JSON object containing `drafts`, `count`, `filter`,
97+
`offset`, and `limit`. Errors are also JSON when `--json` is present.
98+
Export JSON contains `action`, `draft_id`, `markdown`, and
99+
`unsupported_nodes`.
100+
101+
## Publish and delete
102+
103+
```bash
104+
substack drafts publish 12345 --no-send
105+
substack drafts delete 12345 --yes
106+
```
107+
108+
Publishing and deletion require confirmation. JSON and noninteractive
109+
workflows must pass `--yes`. Publishing runs Substack's prepublish check first.
110+
See [safety and publishing behavior](safety.md).
111+
112+
The original console scripts remain supported. See
113+
[legacy CLI commands](legacy-cli.md).

‎docs/index.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ publish, or delete it.
3030
- [Low-level Python API](low-level-api.md)
3131
- [YAML drafts](yaml.md)
3232
- [Compatibility policy](compatibility.md)
33+
- [Release checklist](releasing.md)
3334
- [Changelog](https://github.com/ma2za/python-substack/blob/main/CHANGELOG.md)
34-
- [Release roadmap](https://github.com/ma2za/python-substack/blob/main/ROADMAP.md)

‎docs/python-sdk.md‎

Lines changed: 75 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,75 @@
1-
---
2-
layout: default
3-
title: Python SDK
4-
---
5-
6-
# Python SDK
7-
8-
Use `Api.create_draft_from_markdown` for the same draft-only workflow as the
9-
CLI:
10-
11-
```python
12-
import os
13-
14-
from dotenv import load_dotenv
15-
from substack import Api
16-
17-
load_dotenv()
18-
19-
api = Api(
20-
cookies_path=os.getenv("COOKIES_PATH"),
21-
publication_url=os.getenv("PUBLICATION_URL"),
22-
)
23-
24-
result = api.create_draft_from_markdown(
25-
title="Shipping with Python",
26-
subtitle="A draft created from a script",
27-
markdown="# Hello\n\nThis is **Markdown**.",
28-
tags=["python", "automation"],
29-
)
30-
31-
print(result["draft"]["id"])
32-
```
33-
34-
The method creates an unpublished draft by default. It publishes only when
35-
`publish=True` is passed. It can also set audience, comment permissions, SEO
36-
metadata, slug, section, and tags.
37-
38-
## Export a draft without modifying it
39-
40-
```python
41-
from pathlib import Path
42-
43-
result = api.export_draft_to_markdown(12345)
44-
45-
Path("backup.md").write_text(result["markdown"], encoding="utf-8")
46-
print(result["unsupported_nodes"])
47-
```
48-
49-
The result contains the original `draft`, exported `markdown`, and an
50-
`unsupported_nodes` list. The method performs one draft read and no server
51-
writes. Unsupported editor nodes remain embedded in the Markdown as opaque,
52-
decodable markers.
53-
54-
For supported syntax, see the [Markdown reference](markdown.md). For direct
55-
editor-node construction, see the [low-level Python API](low-level-api.md).
56-
57-
## Update a draft safely
58-
59-
Preview an update before writing it:
60-
61-
```python
62-
preview = api.update_draft_from_markdown(
63-
12345,
64-
"# Revised title\n\nUpdated body.",
65-
dry_run=True,
66-
)
67-
print(preview["payload"])
68-
```
69-
70-
Run the same call without `dry_run=True` to write the update. Unsupported
71-
editor nodes exported as `python-substack-node:v1` markers are preserved by
72-
default. Removing or changing them raises `ValueError` unless
73-
`allow_unsupported_change=True` is deliberately supplied.
1+
---
2+
layout: default
3+
title: Python SDK
4+
---
5+
6+
# Python SDK
7+
8+
Use `Api.create_draft_from_markdown` for the same draft-only workflow as the
9+
CLI:
10+
11+
```python
12+
import os
13+
14+
from dotenv import load_dotenv
15+
from substack import Api
16+
17+
load_dotenv()
18+
19+
api = Api(
20+
cookies_path=os.getenv("COOKIES_PATH"),
21+
publication_url=os.getenv("PUBLICATION_URL"),
22+
)
23+
24+
result = api.create_draft_from_markdown(
25+
title="Shipping with Python",
26+
subtitle="A draft created from a script",
27+
markdown="# Hello\n\nThis is **Markdown**.",
28+
tags=["python", "automation"],
29+
)
30+
31+
print(result["draft"]["id"])
32+
```
33+
34+
The method creates an unpublished draft by default. It publishes only when
35+
`publish=True` is passed. It can also set audience, comment permissions, SEO
36+
metadata, slug, section, and tags.
37+
38+
## Export a draft without modifying it
39+
40+
```python
41+
from pathlib import Path
42+
43+
result = api.export_draft_to_markdown(12345)
44+
45+
Path("backup.md").write_text(result["markdown"], encoding="utf-8")
46+
print(result["unsupported_nodes"])
47+
```
48+
49+
The result contains the original `draft`, exported `markdown`, and an
50+
`unsupported_nodes` list. The method performs one draft read and no server
51+
writes. Unsupported editor nodes remain embedded in the Markdown as opaque,
52+
decodable markers.
53+
54+
For supported syntax, see the [Markdown reference](markdown.md). For direct
55+
editor-node construction, see the [low-level Python API](low-level-api.md).
56+
57+
## Update a draft safely
58+
59+
Preview an update before writing it:
60+
61+
```python
62+
preview = api.update_draft_from_markdown(
63+
12345,
64+
"# Revised title\n\nUpdated body.",
65+
dry_run=True,
66+
)
67+
print(preview["payload"])
68+
```
69+
70+
Run the same call without `dry_run=True` to write the update. Unsupported
71+
editor nodes exported as `python-substack-node:v1` markers are preserved by
72+
default. Removing or changing them raises `ValueError` unless
73+
`allow_unsupported_change=True` is deliberately supplied. Similarly, image
74+
replacements that cannot resolve to their original remote source require
75+
`allow_image_replacement=True`.

‎docs/releases/0.9.0.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ parent: Releases
99
## Added
1010

1111
- Image metadata markers preserve editor image attributes during Markdown
12-
export and update.
12+
export and update with `python-substack-image:v1` markers.
13+
- Add `allow_image_replacement=False` and
14+
`substack drafts update --allow-image-replacement` for explicit image
15+
replacement authorization.
1316
- Draft updates preserve uniquely matched remote image attributes and require
1417
explicit authorization for unmatched replacements.
18+
19+
## Fixed
20+
21+
- Prevent unintended replacement of remote images with editor metadata during
22+
Markdown draft updates.

0 commit comments

Comments
 (0)