Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5a3f75d
docs: re-init CLAUDE.md and align all four harness prompts
OriNachum Sep 12, 2026
e61b4dc
docs: correct the assign-to-workforce worktree citation in CLAUDE.md
OriNachum Sep 12, 2026
dd96960
docs: address Qodo review — planned markers, lint prereq, contract pr…
OriNachum Sep 12, 2026
26201b0
spec: substack-domain-surface (devague /scope + /think)
OriNachum Sep 12, 2026
fd769a7
spec: challenge pass on substack-domain-surface (devague /challenge)
OriNachum Sep 12, 2026
ecb6f8f
plan: substack-domain-surface (devague /spec-to-plan)
OriNachum Sep 12, 2026
a88345a
plan: implementation split (gate 2 approved)
OriNachum Sep 13, 2026
24340ca
t1: record pre-feature learn baseline
OriNachum Sep 13, 2026
9e41d55
merge t1: record pre-feature learn baseline
OriNachum Sep 13, 2026
9dc39af
t4: untrusted third-party text renderer
OriNachum Sep 13, 2026
e292a95
merge t4: untrusted third-party text renderer
OriNachum Sep 13, 2026
0770d0c
t3: webglass subprocess adapter
OriNachum Sep 13, 2026
2af1efd
t2: stdlib HTTP transport with host validation and GET-only backoff
OriNachum Sep 13, 2026
923fe7f
merge t2: stdlib HTTP transport
OriNachum Sep 13, 2026
395562a
merge t3: webglass subprocess adapter
OriNachum Sep 13, 2026
10f6dca
t6: post noun read side (list, get, overview)
OriNachum Sep 13, 2026
38340e8
t5: account noun (whoami auth probe, overview)
OriNachum Sep 13, 2026
7aa21d3
merge t6: post noun read side
OriNachum Sep 13, 2026
6264d93
t12: observed Substack API endpoints for the v1 verbs
OriNachum Sep 13, 2026
0f7df88
merge t12: observed Substack API endpoints
OriNachum Sep 13, 2026
d3cc282
t12: reword a query-string example the secrets gate flagged
OriNachum Sep 13, 2026
f9ffcd1
t5: whoami reads user_id from /api/v1/subscription
OriNachum Sep 13, 2026
71b0352
merge t5: account noun (whoami, overview)
OriNachum Sep 13, 2026
36729f5
t9: feed noun (read, overview)
OriNachum Sep 13, 2026
fee61b3
merge t9: feed noun (read, overview)
OriNachum Sep 13, 2026
d4f2009
t7: comment noun (list, reply, delete, overview)
OriNachum Sep 13, 2026
4cc923a
t8: reaction noun (list, add, remove, overview)
OriNachum Sep 13, 2026
43c0bfb
merge t7: comment noun (list, reply, delete, overview)
OriNachum Sep 13, 2026
06487de
merge t8: reaction noun (list, add, remove, overview)
OriNachum Sep 13, 2026
2af432b
t10: post write side (publish, schedule, unpublish, delete) and markd…
OriNachum Sep 13, 2026
3c85083
merge t10: post write side and markdown body builder
OriNachum Sep 13, 2026
39f95b0
t13: wire the five Substack nouns into the parser, learn and explain
OriNachum Sep 13, 2026
531b712
merge t13: wire the five nouns into parser, learn and explain
OriNachum Sep 13, 2026
4e6999e
t15: bump version to 0.10.0
OriNachum Sep 13, 2026
74fc94e
http: descriptive User-Agent and fail-fast on non-retryable 4xx
OriNachum Sep 13, 2026
c5fa2c4
merge t15: bump version to 0.10.0
OriNachum Sep 13, 2026
6c37c06
t16: CI gates — socket-blocking fixture, repo invariants, coverage floor
OriNachum Sep 13, 2026
7747dbf
merge t16: CI gates (socket-blocking fixture, repo invariants, covera…
OriNachum Sep 13, 2026
fabe98a
t14: document the Substack surface across README and the four harness…
OriNachum Sep 13, 2026
3d13f7b
merge t14: document the Substack surface across README and the four h…
OriNachum Sep 13, 2026
a54b9b3
t17: public-half live proof against two publication hosts
OriNachum Sep 13, 2026
1208162
delivery: summarize the substack-domain-surface workforce run
OriNachum Sep 13, 2026
9690561
merge origin/main into feat/substack-surface (resolve docs/version co…
OriNachum Sep 13, 2026
180dedb
fix: address Qodo review on PR #5 (comments 3998832718, 3998832722, 3…
OriNachum Sep 13, 2026
7121103
merge pr5/qodo-fixes: address Qodo review on PR #5
OriNachum Sep 13, 2026
b55c369
refactor: address SonarCloud smells on the Substack surface (S1192, S…
OriNachum Sep 13, 2026
7dd2d75
refactor: drop redundant HTTPError from except tuples (S5713); split …
OriNachum Sep 13, 2026
d1db98b
refactor: single-return account handlers (S3516), split map_failure's…
OriNachum Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .devague/current
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
substack-domain-surface
1 change: 1 addition & 0 deletions .devague/current_plan
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
substack-domain-surface
543 changes: 543 additions & 0 deletions .devague/deliveries/substack-domain-surface.json

Large diffs are not rendered by default.

1,555 changes: 1,555 additions & 0 deletions .devague/frames/substack-domain-surface.json

Large diffs are not rendered by default.

832 changes: 832 additions & 0 deletions .devague/plans/substack-domain-surface.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:

- run: uv sync

- run: uv run pytest -n auto --cov=substack_cli --cov-report=xml:coverage.xml --cov-report=term -v
- run: uv run pytest -n auto --cov=substack_cli --cov-report=xml:coverage.xml --cov-report=term --cov-fail-under=60 -v

- name: SonarCloud Scan
if: env.SONAR_TOKEN != ''
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,7 @@ skills.local.yaml
# .qwen/settings.json.example to .qwen/settings.json locally, never commit it.
.qwen/settings.json

# devague working state (not committed by default)
.devague/reviews/

.devague/questions/
35 changes: 27 additions & 8 deletions AGENTS.colleague.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,40 @@ defect in this repo.)
## What this project is

`substack-cli` is an **agent-first CLI to manage a Substack publication and
account** *(planned — see Status below)* — publish and schedule posts, read
posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not
account** publish and schedule posts, read posts and comments, react to
posts/comments, and read the account feed. Unofficial community tool, not
affiliated with Substack.

**Status: scaffold.** None of that domain surface exists on disk yet. What is
checked in is the AgentCulture sibling baseline this repo was scaffolded from:
the CLI skeleton (`whoami`, `learn`, `explain`, `overview`, `doctor`,
`cli overview`), a mesh identity, the vendored skill kit, and a build/deploy
baseline. If a brief assumes a posts/subscribers/stats module exists, say so
rather than inventing where it lives.
Five nouns are wired: `account`, `post`, `comment`, `reaction`, `feed`. The
AgentCulture sibling baseline this repo was scaffolded from is still
underneath: the CLI skeleton (`whoami`, `learn`, `explain`, `overview`,
`doctor`, `cli overview`), a mesh identity, the vendored skill kit, and a
build/deploy baseline. If a brief assumes a verb this repo does not have
(e.g. subscriber management or audience statistics), say so rather than
inventing where it lives.

`CLAUDE.md` is written for a Claude Code session working *on* the repo — it is
not your runtime prompt, but it is the fullest write-up of the conventions if
you need more context than fits here.

## Substack surface

The domain layer is `substack_cli/substack/`: `http.py` (stdlib HTTP for the
public read verbs — `post list`/`get`, `comment list`, `reaction list`, no
session needed), `webglass.py` (subprocess wrapper around the sibling
`webglass-cli` project's `webglass` binary for owner verbs — `post
publish`/`schedule`/`unpublish`/`delete`, `comment reply`/`delete`, `reaction
add`/`remove`, `feed read`, `account whoami`), and `render.py`/`body.py`
(ProseMirror body construction). Owner verbs need
`$SUBSTACK_WEBGLASS_SESSION` naming a session already logged in to Substack;
until `webglass-cli` can create such a session itself
(`agentculture/webglass-cli#17`), owner verbs exit `2` with a hint rather than
fail unexplained. `post publish` is draft-first — without `--send` it only
creates a draft; `--send` alone emails every subscriber and cannot be
recalled. Every endpoint the CLI calls must appear in
[`docs/api/substack-endpoints.md`](docs/api/substack-endpoints.md) before it
ships. Tests for this layer use fakes under `tests/fakes/`.

## Contracts to respect when you touch code

These are enforced by CI and by `tests/`, so violating one turns your diff into
Expand Down
42 changes: 30 additions & 12 deletions AGENTS.override.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ what the repo is and how it is laid out, not who is reading it.
## What this project is

`substack-cli` is an **agent-first CLI to manage a Substack publication and
account** *(planned — see Status below)* — publish and schedule posts, read
posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not
account** publish and schedule posts, read posts and comments, react to
posts/comments, and read the account feed. Unofficial community tool, not
affiliated with Substack.

**Status: scaffold — and this matters for every answer you give about the
repo.** None of that Substack surface exists on disk yet. What is checked in
today is the AgentCulture sibling baseline this repo was scaffolded from
(`culture-agent-template`): an agent-first CLI skeleton (`whoami`, `learn`,
`explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the vendored
skill kit, and a build/deploy baseline. If you are asked where posts,
subscribers, comments or statistics are implemented, the honest answer is that
they are not — say so and point at what *is* there, rather than inferring an
implementation from the project description, the README, or this file.
Five nouns are wired on disk: `account`, `post`, `comment`, `reaction`,
`feed` (see [Substack surface](#substack-surface) below). The AgentCulture
sibling baseline this repo was scaffolded from (`culture-agent-template`) is
still underneath: an agent-first CLI skeleton (`whoami`, `learn`, `explain`,
`overview`, `doctor`, `cli overview`), a mesh identity, the vendored skill
kit, and a build/deploy baseline. If you are asked where posts, comments, or
reactions are implemented, point at `substack_cli/cli/_commands/` and
`substack_cli/substack/` rather than inferring from the project description
alone — and if a question assumes a verb this repo does not have (e.g.
subscriber management or audience statistics), say so rather than guessing at
one.

It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster)
(the skills supplier), [`steward`](https://github.com/agentculture/steward)
Expand Down Expand Up @@ -81,16 +83,32 @@ requires nor changes that declaration.
`backend: colleague` with `model: associate`. That is a per-repo choice; this
one does not ship it.)

## Substack surface

Public read verbs (`post list`/`get`, `comment list`, `reaction list`) are
stdlib HTTP with no session. Owner verbs (`post publish`/`schedule`/
`unpublish`/`delete`, `comment reply`/`delete`, `reaction add`/`remove`,
`feed read`, `account whoami`) shell out to the `webglass` binary (sibling
project `webglass-cli`) and need `$SUBSTACK_WEBGLASS_SESSION` naming a
session whose browser is logged in to Substack; until `webglass-cli` can
create such a session (`agentculture/webglass-cli#17`), owner verbs exit `2`
with a hint. Every endpoint the CLI calls must appear in
[`docs/api/substack-endpoints.md`](docs/api/substack-endpoints.md) before it
ships — that is the only record of what Substack's unpublished API does.

## Layout (what you can read/find/summarize here)

```text
substack_cli/ agent-first CLI (cited from teken's python-cli reference)
cli/ parser, error/output contract, _commands/ (verbs)
substack/ domain layer: http.py, webglass.py, render.py, body.py
explain/ markdown catalog for `explain`
tests/ CLI smoke, introspection, harness-registry, script tests
tests/fakes/ fakes for the Substack domain layer
scripts/ scan-secrets.py, harness-smoke.py (both CI gates)
.claude/skills/ vendored guildmaster skill kit (cite-don't-import)
docs/ skill provenance + the four-harness contract/verification
docs/ skill provenance, four-harness contract/verification,
docs/api/substack-endpoints.md (observed endpoint map)
culture.yaml mesh identity (suffix + backend)
.github/workflows/ tests.yml (test/lint/harness-smoke/version-check), publish.yml
```
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.10.0] - 2026-09-13

### Added

- **`account` noun** — `whoami` (probe the webglass session against a
publication's API and report the authenticated account, three-state: no
session, dead session, authenticated) and `overview` (report webglass
presence/version; never fails on a missing install).
- **`post` noun** — `list`, `get`, `publish`, `schedule`, `unpublish`,
`delete`, and `overview`.
- **`comment` noun** — `list` (public), `reply` (top-level or threaded via
`--parent`, owner-only via webglass), `delete` (owner-only via webglass),
and `overview`.
- **`reaction` noun** — `list` (public aggregate counts), `add` and `remove`
(post or comment, owner-only via webglass), and `overview`.
- **`feed` noun** — `read` and `overview`.
- `substack_cli/substack/http.py` — a stdlib-only HTTP transport (no
third-party dependency) for the public, unauthenticated Substack API calls.
- `substack_cli/substack/webglass.py` — a subprocess adapter that shells out
to `webglass-cli` for every owner-scoped call (session-backed reads and all
writes), keeping credential handling out of this process.
- `substack_cli/substack/render.py` — an untrusted-text renderer for post,
comment, and feed content pulled from the Substack API.
- `substack_cli/substack/body.py` — a markdown-to-ProseMirror body builder for
post creation and updates.
- `docs/api/substack-endpoints.md` — the endpoint reference the `http.py` and
`webglass.py` clients are written against.

### Notes

- Owner-scoped verbs (`account whoami`, `post publish`/`schedule`/`unpublish`/
`delete`, `comment reply`/`delete`, `reaction add`/`remove`) report exit `2`
until `webglass-cli` ships authenticated sessions
(agentculture/webglass-cli#17).

## [0.9.1] - 2026-09-12

### Changed
Expand Down
60 changes: 41 additions & 19 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this project is

`substack-cli` is an **agent-first CLI to manage a Substack publication and
account** *(planned — see Status below)* — publish and schedule posts, read
posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not
account** publish and schedule posts, read posts and comments, react to
posts/comments, and read the account feed. Unofficial community tool, not
affiliated with Substack.

**Status: scaffold.** None of that domain surface exists on disk yet. What is
checked in today is the AgentCulture sibling baseline this repo was scaffolded
from (`culture-agent-template`): the agent-first CLI skeleton (`whoami`,
`learn`, `explain`, `overview`, `doctor`, `cli overview`), a mesh identity, the
vendored guildmaster skill kit, and a buildable/deployable package baseline.
The Substack nouns/verbs are the work ahead — see
[Adding the Substack surface](#adding-the-substack-surface-planned).
Five nouns are wired: `account`, `post`, `comment`, `reaction`, `feed`. Public
read verbs (`post list`/`get`, `comment list`, `reaction list`) are stdlib
HTTP with no session. Owner verbs (`post publish`/`schedule`/`unpublish`/
`delete`, `comment reply`/`delete`, `reaction add`/`remove`, `feed read`,
`account whoami`) shell out to the `webglass` binary (sibling project
`webglass-cli`) and need `$SUBSTACK_WEBGLASS_SESSION` naming a session whose
browser is already logged in to Substack — see
[Substack surface](#substack-surface) below. The AgentCulture sibling baseline
this repo was scaffolded from (`culture-agent-template`) is still underneath:
the agent-first CLI skeleton (`whoami`, `learn`, `explain`, `overview`,
`doctor`, `cli overview`), a mesh identity, the vendored guildmaster skill kit,
and a buildable/deployable package baseline.

It is a sibling to [`guildmaster`](https://github.com/agentculture/guildmaster)
(the **skills supplier**), [`steward`](https://github.com/agentculture/steward)
Expand Down Expand Up @@ -97,16 +102,31 @@ every command takes `--json`; any noun with action-verbs must also expose
/no/such/path` exits `0` — see `_commands/overview.py`). `learn` must keep
covering purpose, command map, exit codes, `--json`, and `explain`.

## Adding the Substack surface (planned)
## Substack surface

The domain layer lives in `substack_cli/substack/`: `http.py` (stdlib HTTP for
the public read verbs), `webglass.py` (subprocess wrapper around the
`webglass` binary for owner verbs), `render.py` and `body.py` (ProseMirror
body construction for post/comment writes). Tests for it live under
`tests/fakes/`. Owner verbs currently exit `2` with a hint: `webglass-cli`
cannot yet create an authenticated, headed-login session
(`agentculture/webglass-cli#17`), only drive an existing one. `post publish`
is draft-first — without `--send` it only creates a draft; `--send
--no-email` publishes without notifying subscribers; `--send` alone emails
every subscriber and cannot be recalled.

Work forwards from the existing shape, not around it: a new noun is a module
under `cli/_commands/` with `register(sub)`, a line in `_build_parser()`, a
catalog entry in `explain/catalog.py`, a row in `learn.py`'s text **and** JSON
payload, and tests. Credentials (Substack session cookies / API tokens) must
come from the environment — `scripts/scan-secrets.py` runs in CI and fails on
committed credentials and non-localhost endpoints. For anything non-trivial,
use `/think` → `/spec-to-plan` before writing code; that is what the vendored
devague skills are here for.
payload, and tests. Every endpoint the CLI calls must appear in
[`docs/api/substack-endpoints.md`](docs/api/substack-endpoints.md) before it
ships — that file is the only record of what Substack's unpublished API
actually does, observed against a real logged-in session. Credentials
(Substack session cookies / API tokens) must come from the environment —
`scripts/scan-secrets.py` runs in CI and fails on committed credentials and
non-localhost endpoints. For anything non-trivial, use `/think` →
`/spec-to-plan` before writing code; that is what the vendored devague skills
are here for.

## Identity and the four harnesses

Expand Down Expand Up @@ -183,20 +203,22 @@ scripts; a fix belongs upstream, then re-sync per `docs/skill-sources.md`.
(`.github/workflows/publish.yml`); PRs do a TestPyPI dry-run. Configure the
`pypi` / `testpypi` GitHub environments and a PyPI Trusted Publisher before
the publish job can succeed.
- Keep this file grounded in **checked-in reality**. The Substack surface is
aspirational today; anything that runs ahead of disk goes under a `(planned)`
marker or a `## Roadmap` heading.
- Keep this file grounded in **checked-in reality**. Anything that runs ahead
of disk goes under a `(planned)` marker or a `## Roadmap` heading.

## Layout

```text
substack_cli/ agent-first CLI (cited from teken's python-cli reference)
cli/ parser, error/output contract, _commands/ (verbs)
substack/ domain layer: http.py, webglass.py, render.py, body.py
explain/ markdown catalog for `explain`
tests/ CLI smoke, introspection, harness-registry, script tests
tests/fakes/ fakes for the Substack domain layer
scripts/ scan-secrets.py, harness-smoke.py (both CI gates)
.claude/skills/ vendored guildmaster skill kit (cite-don't-import)
docs/ skill provenance + the four-harness contract/verification
docs/ skill provenance, four-harness contract/verification,
docs/api/substack-endpoints.md (observed endpoint map)
culture.yaml mesh identity (suffix + backend)
.github/workflows/ tests.yml (test/lint/harness-smoke/version-check), publish.yml
```
Expand Down
Loading
Loading