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.
Files changed (67) hide show
  1. {pubkit-0.2.0 → pubkit-0.3.0}/CHANGELOG.md +41 -0
  2. {pubkit-0.2.0 → pubkit-0.3.0}/PKG-INFO +46 -9
  3. {pubkit-0.2.0 → pubkit-0.3.0}/README.md +45 -8
  4. {pubkit-0.2.0 → pubkit-0.3.0}/docs/FAILURE-MODES.md +69 -0
  5. {pubkit-0.2.0 → pubkit-0.3.0}/pyproject.toml +1 -1
  6. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/__init__.py +1 -1
  7. pubkit-0.3.0/src/pubkit/browserctl.py +490 -0
  8. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/cli.py +59 -9
  9. pubkit-0.3.0/src/pubkit/core/login.py +430 -0
  10. pubkit-0.3.0/tests/fixtures/login_server.py +153 -0
  11. pubkit-0.3.0/tests/test_login.py +412 -0
  12. pubkit-0.2.0/src/pubkit/browserctl.py +0 -213
  13. {pubkit-0.2.0 → pubkit-0.3.0}/.gitignore +0 -0
  14. {pubkit-0.2.0 → pubkit-0.3.0}/LICENSE +0 -0
  15. {pubkit-0.2.0 → pubkit-0.3.0}/NOTICE +0 -0
  16. {pubkit-0.2.0 → pubkit-0.3.0}/docs/ADAPTERS.md +0 -0
  17. {pubkit-0.2.0 → pubkit-0.3.0}/docs/ARCHITECTURE.md +0 -0
  18. {pubkit-0.2.0 → pubkit-0.3.0}/docs/QUICKSTART.md +0 -0
  19. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-01-model-sizes.png +0 -0
  20. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-02-cpu-vs-gpu-ANIMATED.gif +0 -0
  21. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-03-starving-cores-ANIMATED.gif +0 -0
  22. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part1-04-gpu-comparison.png +0 -0
  23. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-01-one-token-one-read-ANIMATED.gif +0 -0
  24. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-02-decode-ceilings.png +0 -0
  25. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-03-prefill-vs-decode.png +0 -0
  26. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-04-kv-cache-cost.png +0 -0
  27. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-05-batching-ANIMATED.gif +0 -0
  28. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-06-vram-budget.png +0 -0
  29. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part2-07-throughput-latency.png +0 -0
  30. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-01-interconnect.png +0 -0
  31. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-02-llmd-request-path.png +0 -0
  32. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-03-llmd-objects.png +0 -0
  33. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/img/part3-04-slos.png +0 -0
  34. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-1.md +0 -0
  35. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-2.md +0 -0
  36. {pubkit-0.2.0 → pubkit-0.3.0}/examples/inside-ai-infra/part-3.md +0 -0
  37. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/__main__.py +0 -0
  38. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/__init__.py +0 -0
  39. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/api_base.py +0 -0
  40. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/devto.py +0 -0
  41. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/medium.py +0 -0
  42. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/substack.py +0 -0
  43. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/adapters/x.py +0 -0
  44. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/__init__.py +0 -0
  45. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/adapter.py +0 -0
  46. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/anchors.py +0 -0
  47. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/assets.py +0 -0
  48. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/auth.py +0 -0
  49. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/browser.py +0 -0
  50. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/capabilities.py +0 -0
  51. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/checks.py +0 -0
  52. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/ir.py +0 -0
  53. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/loader.py +0 -0
  54. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/runner.py +0 -0
  55. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/state.py +0 -0
  56. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/core/transport.py +0 -0
  57. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/py.typed +0 -0
  58. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/registry.py +0 -0
  59. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/__init__.py +0 -0
  60. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/html.py +0 -0
  61. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/render/tables.py +0 -0
  62. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/scaffold.py +0 -0
  63. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/workflows/__init__.py +0 -0
  64. {pubkit-0.2.0 → pubkit-0.3.0}/src/pubkit/workflows/airflow.py +0 -0
  65. {pubkit-0.2.0 → pubkit-0.3.0}/tests/fixtures/fake_editor.html +0 -0
  66. {pubkit-0.2.0 → pubkit-0.3.0}/tests/test_browser.py +0 -0
  67. {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.2.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. pubkit watches for the
150
- post-login URL, saves the session encrypted, and closes the window.
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.1. The core, the checks, the planner and the state machine are tested (32
317
- tests) and exercised end-to-end against a real published series in
318
- `examples/inside-ai-infra/`. The browser adapters encode techniques verified by
319
- hand against live editors; selectors are the part most likely to need a patch
320
- when a platform ships a redesign, which is exactly why they are isolated in one
321
- dataclass per adapter.
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. pubkit watches for the
100
- post-login URL, saves the session encrypted, and closes the window.
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.1. The core, the checks, the planner and the state machine are tested (32
267
- tests) and exercised end-to-end against a real published series in
268
- `examples/inside-ai-infra/`. The browser adapters encode techniques verified by
269
- hand against live editors; selectors are the part most likely to need a patch
270
- when a platform ships a redesign, which is exactly why they are isolated in one
271
- dataclass per adapter.
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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pubkit"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Publish one source to many platforms, safely. Medium, Substack, X, Dev.to, Hashnode."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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.2.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",