@vornrun/connector-substack 0.1.0

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@vornrun/connector-substack`.
4
+
5
+ ## 0.1.0
6
+
7
+ First release.
8
+
9
+ Trigger a workflow from new posts on a Substack publication, and let a
10
+ workflow step read any publication's feed, search posts, read a post's
11
+ comments, save a draft from markdown, comment on a post, like or unlike a
12
+ comment, and delete a draft or a comment.
13
+
14
+ - **Trigger:** `newPost`, from the publication's RSS feed.
15
+ - **Actions:** `readFeed`, `searchPosts`, `readComments`, `createDraft`,
16
+ `deleteDraft`, `commentOnPost`, `setCommentLike`, `deleteComment`.
17
+ - **Signing in:** the first connector on the `browser` rung. Vorn opens a
18
+ window on a browser profile kept for the connection alone, and every
19
+ signed-in call runs inside that window as a request from Substack's own
20
+ page. No cookie or password reaches the connector.
21
+
22
+ Substack publishes no API, so the connector reads the public RSS feed and
23
+ asks the web endpoints Substack's pages use, each one checked from a
24
+ signed-in browser on 2026-09-10 against the author's own publication.
25
+ Reading the feed, searching and reading comments on a public post need no
26
+ sign-in. Writes stay on `*.substack.com`.
27
+
28
+ It never publishes or schedules, because publishing emails every
29
+ subscriber: any request path containing `publish` or `schedule` is refused
30
+ before it leaves, and `deleteDraft` refuses a published post. Draft bodies
31
+ are markdown converted to the editor's own document, using only the node and
32
+ mark names Substack's editor saved.
33
+
34
+ Needs the Vorn release that adds browser sign-in; an older Vorn does not
35
+ list the connector. Ships as a pack with a conformance receipt covering the
36
+ dedupe replay of the trigger and the mock run of every action. `marked`
37
+ travels inside the bundle; no runtime dependencies.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @vornrun/connector-substack
2
+
3
+ Trigger Vorn workflows from new posts on a Substack publication, and read a
4
+ feed, search posts, read comments, save a draft, comment, and like or delete
5
+ comments from a workflow step. Substack publishes no API for any of this, so
6
+ the connector reads the public RSS feed and asks the same web endpoints
7
+ Substack's own pages use, from inside a window you signed in to.
8
+
9
+ ## Signing in
10
+
11
+ Connect, then sign in to Substack in the window Vorn opens. The window runs
12
+ on a browser profile kept for this connection alone. Vorn keeps that
13
+ profile, not a password and not a copied cookie, and the connection's row
14
+ says who is signed in. A sign-in link Substack emails opens in your default
15
+ browser; paste it into the connection's sign-in link field instead.
16
+
17
+ The connector never sees a cookie. Each signed-in call runs inside that
18
+ window as a request from Substack's own page, on `substack.com` or a
19
+ `*.substack.com` publication, and Vorn refuses any other address.
20
+
21
+ When Substack signs the window out, a workflow step that needed it waits as
22
+ "waiting for sign-in" until you sign in again, then runs again. When Vorn is
23
+ closed on the desktop that signed in, the step fails and says to open it.
24
+
25
+ Reading a feed, searching posts and reading the comments on a public post
26
+ need no sign-in.
27
+
28
+ ## Settings
29
+
30
+ | Setting | Env | What it is |
31
+ | --- | --- | --- |
32
+ | `publication` | `SUBSTACK_PUBLICATION` | Your publication's substack.com subdomain, such as `novumai`. The `newPost` trigger reads its feed, and a step that names no publication uses it. When it is empty, signed-in actions use the account's primary publication. |
33
+
34
+ ## It never publishes
35
+
36
+ Publishing a post emails every subscriber, so nothing here publishes or
37
+ schedules. `createDraft` saves a draft and returns the address to open it in
38
+ the editor, where you publish it yourself. Any request path containing
39
+ `publish` or `schedule` is refused before it leaves, and `deleteDraft` reads
40
+ the draft first and refuses a published post.
41
+
42
+ ## Substack's terms
43
+
44
+ Substack's [terms of use](https://substack.com/tos) forbid automated crawling
45
+ and unattended processes. Keep the signed-in actions in workflows you start,
46
+ and poll the feed no more often than a reader would.
47
+
48
+ ## Triggers
49
+
50
+ ### `newPost` — a post appears in the publication's feed
51
+
52
+ Reads `https://<publication>/feed` over plain HTTPS without signing in, on the
53
+ SDK's `timestamp` dedupe from each item's `pubDate`. The feed holds about the
54
+ last twenty posts, so a burst larger than that between polls loses the
55
+ oldest. Each item carries `externalId` (the guid, which is the post's
56
+ address), `title`, `url`, `description` (the subtitle), `assignee` (the
57
+ author) and `updatedAt`, plus `subtitle`, `author`, `publishedAt` and `text`
58
+ for templates.
59
+
60
+ ## Actions
61
+
62
+ | Action | Idempotent | Signed in | What it does |
63
+ | --- | --- | --- | --- |
64
+ | `readFeed` | yes | no | `GET https://<publication>/feed`, for any publication, a custom domain included. `limit` from 1 to 20, default 10. Returns `publication`, `count` and `posts` (`id`, `title`, `subtitle`, `url`, `author`, `publishedAt`, `html`, `text`). |
65
+ | `searchPosts` | yes | no | `GET substack.com/api/v1/post/search` with `query` and `page` (from 0). Returns `count`, `more` and `posts` (`id`, `title`, `subtitle`, `url`, `publishedAt`, `author`, `authorHandle`, `likes`, `comments`, `publicationId`). |
66
+ | `readComments` | yes | no | Looks the post up by address or slug (`GET /api/v1/posts/<slug>`) unless given `postId`, then `GET /api/v1/post/<id>/comments?all_comments=true&sort=newest_first`. Returns `postId`, `count` and `comments` (`id`, `body`, `author`, `handle`, `date`, `likes`, `parentId`, `replies`), each reply after the comment it answers. |
67
+ | `createDraft` | no | yes | `GET substack.com/api/v1/user/profile/self` for the byline, then `POST /api/v1/drafts` on the publication with the markdown body converted to the editor's document. Returns `id`, `title` and `editUrl`. |
68
+ | `deleteDraft` | no | yes | `GET`, then `DELETE /api/v1/drafts/<id>`; a published post is refused. Returns `deleted`. |
69
+ | `commentOnPost` | no | yes | `POST /api/v1/post/<id>/comment` with `{ body }`. Returns `id` and `postId`. |
70
+ | `setCommentLike` | yes | yes | `POST` to like or `DELETE` to unlike `/api/v1/comment/<id>/reaction`, with `{ reaction: "❤" }`. Returns `liked`. |
71
+ | `deleteComment` | no | yes | `DELETE /api/v1/comment/<id>`. Returns `deleted`. |
72
+
73
+ Writes stay on `*.substack.com`. A publication on a custom domain can be read
74
+ at its own address, but a comment on one of its posts is refused; write to it
75
+ at its substack.com address instead.
76
+
77
+ ### Markdown in a draft
78
+
79
+ Headings, paragraphs, bullet and numbered lists, quotes, code blocks, rules,
80
+ line breaks, bold, italic, inline code, strikethrough and links carry over,
81
+ under the node and mark names Substack's editor saved when each was pasted
82
+ into a draft. A picture becomes a link to it, because uploading one is the
83
+ editor's job. A table becomes one line per row, and a script or data link
84
+ keeps its words without being clickable.
85
+
86
+ ## Checks
87
+
88
+ ```sh
89
+ packages/substack/scripts/check.sh # typecheck, tests, build, conformance receipt
90
+ ```
91
+
92
+ Tests make no network calls. Plain and signed-in fetches are separate stubs,
93
+ so each test also says which calls went through the window. There is no live
94
+ check: every signed-in action needs the Vorn window, which the SDK's live
95
+ check skips.
96
+
97
+ ## Built from
98
+
99
+ Substack publishes no API reference. Every request here was made from a
100
+ signed-in browser on 2026-09-10 against the author's own publication, and
101
+ each write was undone.
102
+
103
+ - `GET substack.com/api/v1/user/profile/self` answers 200 with `id`, `name`,
104
+ `handle` and `publicationUsers` when signed in, and 401 when not.
105
+ - `/feed` is RSS 2.0 with `guid`, `pubDate`, `dc:creator` and
106
+ `content:encoded`.
107
+ - Search, comments and a post by slug answer without a sign-in.
108
+ - `POST /api/v1/drafts` answers 400 `draft_bylines Invalid value` without a
109
+ byline and 200 with one. A deleted draft then reads back 404.
110
+ - Commenting, liking and deleting a comment are the requests listed above.
@@ -0,0 +1,5 @@
1
+ import * as _vornrun_connector_sdk from '@vornrun/connector-sdk';
2
+
3
+ declare const connector: _vornrun_connector_sdk.Connector;
4
+
5
+ export { connector, connector as default };