pubkit 0.2.0__tar.gz → 0.3.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.
- {pubkit-0.2.0 → pubkit-0.3.0}/CHANGELOG.md +41 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/PKG-INFO +46 -9
- {pubkit-0.2.0 → pubkit-0.3.0}/README.md +45 -8
- {pubkit-0.2.0 → pubkit-0.3.0}/docs/FAILURE-MODES.md +69 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/pyproject.toml +1 -1
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/__init__.py +1 -1
- pubkit-0.3.0/src/pubkit/browserctl.py +490 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/cli.py +59 -9
- pubkit-0.3.0/src/pubkit/core/login.py +430 -0
- pubkit-0.3.0/tests/fixtures/login_server.py +153 -0
- pubkit-0.3.0/tests/test_login.py +412 -0
- pubkit-0.2.0/src/pubkit/browserctl.py +0 -213
- {pubkit-0.2.0 → pubkit-0.3.0}/.gitignore +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/LICENSE +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/NOTICE +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/docs/ADAPTERS.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/docs/ARCHITECTURE.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/docs/QUICKSTART.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-01-model-sizes.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-02-cpu-vs-gpu-ANIMATED.gif +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-03-starving-cores-ANIMATED.gif +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-04-gpu-comparison.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-01-one-token-one-read-ANIMATED.gif +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-02-decode-ceilings.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-03-prefill-vs-decode.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-04-kv-cache-cost.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-05-batching-ANIMATED.gif +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-06-vram-budget.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-07-throughput-latency.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-01-interconnect.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-02-llmd-request-path.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-03-llmd-objects.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-04-slos.png +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-1.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-2.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-3.md +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/__main__.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/__init__.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/api_base.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/devto.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/medium.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/substack.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/x.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/__init__.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/adapter.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/anchors.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/assets.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/auth.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/browser.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/capabilities.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/checks.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/ir.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/loader.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/runner.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/state.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/transport.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/py.typed +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/registry.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/__init__.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/html.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/tables.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/scaffold.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/workflows/__init__.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/workflows/airflow.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/tests/fixtures/fake_editor.html +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/tests/test_browser.py +0 -0
- {pubkit-0.2.0 → pubkit-0.3.0}/tests/test_core.py +0 -0
|
@@ -4,6 +4,47 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
4
4
|
Versioning is [semantic](https://semver.org/); adapters may change behaviour on
|
|
5
5
|
a minor bump when a platform changes underneath them.
|
|
6
6
|
|
|
7
|
+
## [0.3.0] — 2026-09-11
|
|
8
|
+
|
|
9
|
+
Sign-in is the one step a human performs and a machine has to judge, and every
|
|
10
|
+
way it fails is silent. This release makes each of them say something.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`login_preflight()`** — playwright, browser, host reachability, a writable
|
|
15
|
+
session store and any existing session are checked before a window opens.
|
|
16
|
+
A login that cannot work no longer costs five minutes to find out.
|
|
17
|
+
- **Live observation during sign-in** — console errors, failed requests and
|
|
18
|
+
challenge-host scripts are recorded while the human works, and submissions to
|
|
19
|
+
the platform are counted. A button that never fires is now distinguishable
|
|
20
|
+
from a bot check that never completes; previously both were silence.
|
|
21
|
+
- **Post-flight proof** — the saved state is checked for cookies on the
|
|
22
|
+
platform's own domain and for the cookie names that authorise writing, its
|
|
23
|
+
expiry is reported, and a signed-in page is fetched and checked. **A session
|
|
24
|
+
is written only after it has been shown to work.**
|
|
25
|
+
- **`pubkit auth login <platform> --attach`** — watch a Chrome you started
|
|
26
|
+
yourself (`--remote-debugging-port=9222`) instead of launching one, for
|
|
27
|
+
platforms that will not complete a sign-in in a fresh profile.
|
|
28
|
+
- **`pubkit auth login --minutes`** to set the wait.
|
|
29
|
+
- **`core/login.py`** — one `LoginFlow` per platform declaring success markers,
|
|
30
|
+
required cookies, cookie domain, probe URL and signed-in markers. A new
|
|
31
|
+
platform inherits the whole validation suite rather than new special cases.
|
|
32
|
+
- **`tests/fixtures/login_server.py`** — a sign-in page that fails the six ways
|
|
33
|
+
real ones do: dead button, blocked challenge, partial cookies, short-lived
|
|
34
|
+
session, ghost session, and the happy path. 26 tests cover the matrix; the
|
|
35
|
+
unit half needs no browser at all.
|
|
36
|
+
- **Class E in `docs/FAILURE-MODES.md`** — six new entries.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- A launched login window now uses a **persistent pubkit profile** and real
|
|
41
|
+
Google Chrome where it is installed, falling back to bundled Chromium.
|
|
42
|
+
pubkit does not try to disguise an automated browser; where a platform
|
|
43
|
+
declines one, `--attach` uses the browser you actually sign in with.
|
|
44
|
+
- `pubkit auth verify` runs the identical post-flight checks as `login`, so a
|
|
45
|
+
session cannot pass one and fail the other, and prints each finding.
|
|
46
|
+
- `BROWSER_PLATFORMS` derives from the login flow table — one source of truth.
|
|
47
|
+
|
|
7
48
|
## [0.2.0] — 2026-09-11
|
|
8
49
|
|
|
9
50
|
The release that makes `pubkit publish --to medium --confirm` actually work
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pubkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Publish one source to many platforms, safely. Medium, Substack, X, Dev.to, Hashnode.
|
|
5
5
|
Project-URL: Homepage, https://github.com/arunsingh/pubkit
|
|
6
6
|
Project-URL: Issues, https://github.com/arunsingh/pubkit/issues
|
|
@@ -146,8 +146,41 @@ pubkit auth login medium
|
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
A real browser window opens at Medium's login page. You sign in — password
|
|
149
|
-
manager, MFA, device confirmation, whatever it asks for.
|
|
150
|
-
|
|
149
|
+
manager, MFA, device confirmation, whatever it asks for.
|
|
150
|
+
|
|
151
|
+
Before that window opens, pubkit checks the things that would otherwise cost you
|
|
152
|
+
five silent minutes: playwright, a browser, whether the login host is even
|
|
153
|
+
reachable from here, whether the session store is writable, and whether you
|
|
154
|
+
already have a working session. After you sign in, it checks the cookies are for
|
|
155
|
+
the platform's own domain, that the ones which authorise writing are present,
|
|
156
|
+
how long they last — and then fetches a page only a signed-in user can see.
|
|
157
|
+
**The session is saved only once it has been proven to work.** A saved session
|
|
158
|
+
that does not work is worse than none, because the failure moves into the middle
|
|
159
|
+
of a publish.
|
|
160
|
+
|
|
161
|
+
If the sign-in button does nothing at all — no error, no request, the single
|
|
162
|
+
most confusing failure there is — pubkit says so, and says which of the two
|
|
163
|
+
causes it was:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
✗ timeout: no sign-in seen in 5 minutes
|
|
167
|
+
! last URL: https://medium.com/m/signin
|
|
168
|
+
✗ form submission: the page never sent a sign-in request
|
|
169
|
+
the button was not wired up, or a script it waits on never finished —
|
|
170
|
+
try: pubkit auth login medium --attach
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`--attach` watches a Chrome you started yourself rather than launching one:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
open -a "Google Chrome" --args --remote-debugging-port=9222
|
|
177
|
+
pubkit auth login medium --attach
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
pubkit drives nothing about that sign-in; it watches a tab you are already in.
|
|
181
|
+
It does not try to look like something it is not, and it does not attempt to
|
|
182
|
+
defeat a bot check — where a platform declines an automated browser, `--attach`
|
|
183
|
+
gets out of the way instead.
|
|
151
184
|
|
|
152
185
|
From then on, unattended:
|
|
153
186
|
|
|
@@ -313,12 +346,16 @@ validate >> PubkitPublishOperator.expand_fanout(
|
|
|
313
346
|
|
|
314
347
|
## Status
|
|
315
348
|
|
|
316
|
-
v0.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
349
|
+
v0.3. The core, the checks, the planner, the state machine, the browser pool,
|
|
350
|
+
the table renderer and the sign-in validator are covered by 68 tests, and the
|
|
351
|
+
whole pipeline is exercised end-to-end against a real published series in
|
|
352
|
+
`examples/inside-ai-infra/`. Two hostile fixtures carry most of the weight: an
|
|
353
|
+
editor that strips images, splits content roots and lands figures above the
|
|
354
|
+
caret, and a sign-in page that fails the six ways real ones do.
|
|
355
|
+
|
|
356
|
+
Selectors remain the part most likely to need a patch when a platform ships a
|
|
357
|
+
redesign, which is exactly why they are isolated in one dataclass per adapter —
|
|
358
|
+
and login flows in one `LoginFlow` per platform.
|
|
322
359
|
|
|
323
360
|
## Docs
|
|
324
361
|
|
|
@@ -96,8 +96,41 @@ pubkit auth login medium
|
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
A real browser window opens at Medium's login page. You sign in — password
|
|
99
|
-
manager, MFA, device confirmation, whatever it asks for.
|
|
100
|
-
|
|
99
|
+
manager, MFA, device confirmation, whatever it asks for.
|
|
100
|
+
|
|
101
|
+
Before that window opens, pubkit checks the things that would otherwise cost you
|
|
102
|
+
five silent minutes: playwright, a browser, whether the login host is even
|
|
103
|
+
reachable from here, whether the session store is writable, and whether you
|
|
104
|
+
already have a working session. After you sign in, it checks the cookies are for
|
|
105
|
+
the platform's own domain, that the ones which authorise writing are present,
|
|
106
|
+
how long they last — and then fetches a page only a signed-in user can see.
|
|
107
|
+
**The session is saved only once it has been proven to work.** A saved session
|
|
108
|
+
that does not work is worse than none, because the failure moves into the middle
|
|
109
|
+
of a publish.
|
|
110
|
+
|
|
111
|
+
If the sign-in button does nothing at all — no error, no request, the single
|
|
112
|
+
most confusing failure there is — pubkit says so, and says which of the two
|
|
113
|
+
causes it was:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
✗ timeout: no sign-in seen in 5 minutes
|
|
117
|
+
! last URL: https://medium.com/m/signin
|
|
118
|
+
✗ form submission: the page never sent a sign-in request
|
|
119
|
+
the button was not wired up, or a script it waits on never finished —
|
|
120
|
+
try: pubkit auth login medium --attach
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`--attach` watches a Chrome you started yourself rather than launching one:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
open -a "Google Chrome" --args --remote-debugging-port=9222
|
|
127
|
+
pubkit auth login medium --attach
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
pubkit drives nothing about that sign-in; it watches a tab you are already in.
|
|
131
|
+
It does not try to look like something it is not, and it does not attempt to
|
|
132
|
+
defeat a bot check — where a platform declines an automated browser, `--attach`
|
|
133
|
+
gets out of the way instead.
|
|
101
134
|
|
|
102
135
|
From then on, unattended:
|
|
103
136
|
|
|
@@ -263,12 +296,16 @@ validate >> PubkitPublishOperator.expand_fanout(
|
|
|
263
296
|
|
|
264
297
|
## Status
|
|
265
298
|
|
|
266
|
-
v0.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
299
|
+
v0.3. The core, the checks, the planner, the state machine, the browser pool,
|
|
300
|
+
the table renderer and the sign-in validator are covered by 68 tests, and the
|
|
301
|
+
whole pipeline is exercised end-to-end against a real published series in
|
|
302
|
+
`examples/inside-ai-infra/`. Two hostile fixtures carry most of the weight: an
|
|
303
|
+
editor that strips images, splits content roots and lands figures above the
|
|
304
|
+
caret, and a sign-in page that fails the six ways real ones do.
|
|
305
|
+
|
|
306
|
+
Selectors remain the part most likely to need a patch when a platform ships a
|
|
307
|
+
redesign, which is exactly why they are isolated in one dataclass per adapter —
|
|
308
|
+
and login flows in one `LoginFlow` per platform.
|
|
272
309
|
|
|
273
310
|
## Docs
|
|
274
311
|
|
|
@@ -220,3 +220,72 @@ Publishing one document to five platforms is a fan-out where any leg can fail.
|
|
|
220
220
|
> **pubkit:** per-platform token-bucket limiter, bounded exponential backoff
|
|
221
221
|
> with jitter, and per-leg result reporting. One platform failing never blocks
|
|
222
222
|
> the others; the run exits non-zero with a machine-readable summary.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Class E — Sign-in
|
|
227
|
+
|
|
228
|
+
A login is the only step a human performs and a machine has to judge, and every
|
|
229
|
+
failure in it is silent. These six were all observed against live platforms.
|
|
230
|
+
|
|
231
|
+
### E1. The submit button does nothing at all
|
|
232
|
+
A sign-in page opens, the email is typed, Continue is clicked, and nothing
|
|
233
|
+
happens. No error, no navigation, no network request, nothing in the console.
|
|
234
|
+
It is the hardest login failure to report because from the outside there is
|
|
235
|
+
nothing to report.
|
|
236
|
+
|
|
237
|
+
> **pubkit:** the page is watched while the human works. Every POST/fetch to
|
|
238
|
+
> the platform is counted, so "you did not finish" and "the button never fired"
|
|
239
|
+
> stop being the same silence. Zero submissions after a timeout is reported as
|
|
240
|
+
> a failure in its own right, with `--attach` as the next step.
|
|
241
|
+
|
|
242
|
+
### E2. A bot check that never completes
|
|
243
|
+
Identical symptom to E1, opposite cause: the click *is* handled, but the
|
|
244
|
+
handler waits on a challenge script that a freshly launched browser profile
|
|
245
|
+
never gets a token from. Indistinguishable by eye.
|
|
246
|
+
|
|
247
|
+
> **pubkit:** requests to known challenge hosts (reCAPTCHA, hCaptcha, Arkose,
|
|
248
|
+
> PerimeterX, DataDome, Turnstile) are recorded separately, and a failed one is
|
|
249
|
+
> named in the diagnosis. The remedy is `pubkit auth login <platform> --attach`,
|
|
250
|
+
> which watches a Chrome the user started themselves. pubkit does not attempt
|
|
251
|
+
> to defeat a challenge; it gets out of the way of one.
|
|
252
|
+
|
|
253
|
+
### E3. Waiting five minutes for something that could never happen
|
|
254
|
+
playwright not installed, no browser downloaded, the login host unreachable
|
|
255
|
+
behind a VPN or proxy, a session directory that is not writable. Each produces
|
|
256
|
+
the same blank window and the same long wait.
|
|
257
|
+
|
|
258
|
+
> **pubkit:** `login_preflight()` runs first, synchronously and cheaply. A
|
|
259
|
+
> window is never opened when it cannot work, and each failure names its own
|
|
260
|
+
> fix. It also says when a valid session already exists, so the user is not
|
|
261
|
+
> signed out of something that was working.
|
|
262
|
+
|
|
263
|
+
### E4. The flow stops at the identity provider
|
|
264
|
+
Sign-in goes through Google or a corporate SSO hop, the redirect back never
|
|
265
|
+
completes, and what gets saved is a session for `accounts.google.com` with no
|
|
266
|
+
cookie for the platform at all.
|
|
267
|
+
|
|
268
|
+
> **pubkit:** the saved state is checked for cookies on the platform's own
|
|
269
|
+
> registrable domain, and for the specific cookie names that authorise writing.
|
|
270
|
+
> Missing ones are named.
|
|
271
|
+
|
|
272
|
+
### E5. A session that validates and does not work
|
|
273
|
+
Both session cookies present, correct domain, good expiry — and the signed-in
|
|
274
|
+
page still bounces back to sign-in. Cookie inspection cannot detect this; only
|
|
275
|
+
asking the platform can.
|
|
276
|
+
|
|
277
|
+
> **pubkit:** before anything is written, a page that renders only for a
|
|
278
|
+
> signed-in user is fetched and checked for a signed-in marker. A session is
|
|
279
|
+
> saved only once it has been *proven* to work. A saved session that does not
|
|
280
|
+
> work is worse than none, because it moves the failure into the middle of a
|
|
281
|
+
> publish.
|
|
282
|
+
|
|
283
|
+
### E6. A session that expires quietly
|
|
284
|
+
A session cookie with no persistent expiry dies with the browser; one with a
|
|
285
|
+
two-day expiry dies mid-week. Either way the failure surfaces days later,
|
|
286
|
+
inside a publish, looking like something else.
|
|
287
|
+
|
|
288
|
+
> **pubkit:** expiry is reported at login time, and a lifetime under a week is
|
|
289
|
+
> a warning with the number in it. `pubkit auth verify` re-runs the identical
|
|
290
|
+
> checks on demand — same code path, so a session cannot pass one and fail the
|
|
291
|
+
> other.
|
|
@@ -6,7 +6,7 @@ from .core.loader import load_document, load_series # noqa: F401
|
|
|
6
6
|
from .core.runner import Pipeline, RunReport # noqa: F401
|
|
7
7
|
from .registry import build_adapter, list_adapters, register # noqa: F401
|
|
8
8
|
|
|
9
|
-
__version__ = "0.
|
|
9
|
+
__version__ = "0.3.0"
|
|
10
10
|
__all__ = [
|
|
11
11
|
"Document", "Series", "Asset", "Figure", "Table", "Heading", "Paragraph", "Code",
|
|
12
12
|
"Pipeline", "RunReport", "load_document", "load_series",
|