Skip to main content
Glama

Post Plan

Post Plan is a Cloudflare Worker that publishes complete model-authored HTML plans at post-plan.boyan.live. It stores current plans and revision history in D1, isolates the model document in a hardened viewer, and adds a visible codex://threads/<thread-id> link back to the originating Codex task.

Publishing is designed for a small, invite-only group. Each publisher signs in through a dedicated GitHub OAuth App, receives an independent and revocable Post Plan session, and owns the plans they create. GitHub is used only to establish public account identity: the device flow requests no OAuth scopes, and Post Plan does not retain the GitHub access token.

The repository also contains a Codex plugin that supplies:

  • an implicitly invoked publish-plan planning skill;

  • a dependency-free MCP server with publish_plan and unpublish_plan tools;

  • a trusted UserPromptSubmit hook that passes the current session ID into the workflow;

  • a deliberate replacement for built-in Plan mode in Default mode.

Codex does not expose a plugin API for registering or changing collaboration modes. The hook therefore blocks prompts sent in built-in Plan mode and asks the user to switch to Default, where the plugin can publish the online plan.

Architecture and authorization

One-time login
Local configure script ──> Post Plan Worker ──device flow──> GitHub
        │                         │                           │
        │                         ├── public profile / ID <──┤
        │                         ├── invite check in D1
        │<── Post Plan session ───┤   (GitHub token discarded)

Normal publishing
Codex skill/hook ──> local stdio MCP ──Post Plan session──> Worker
                                                            │
                                                            ├── D1 users, session hashes,
                                                            │   owned plans + revisions
                                                            └── /p/<unlisted-id>
                                                                  └── codex://threads/<uuid>

GitHub device authorization is a login bootstrap, not a dependency for normal publishing. The Worker uses GitHub's immutable numeric user ID for authorization and keeps the current login only as a display snapshot. It requests no email, repository, organization, or write scope. The GitHub access token returned during device login is used only to call GitHub's authenticated-user endpoint and is then discarded.

Post Plan stores only a SHA-256 digest of each opaque API session. Sessions can expire or be revoked without affecting other friends. A plan has one owner: only that owner can revise or revoke it, and a cross-user mutation returns 404 rather than revealing whether the plan exists.

Public plan links remain unlisted, not private. Anyone with a /p/<id> URL can read it, including historical revision URLs. GitHub sign-in protects publishing; it does not protect viewing. The Codex deep link is only a routing identifier and neither contains credentials nor grants access to a task.

The Worker supports three rollout modes:

AUTH_MODE

Accepted write credential

Intended use

legacy

Shared PUBLISH_TOKEN only

Compatibility and rollback only

dual

GitHub-backed Post Plan sessions or PUBLISH_TOKEN

Migration and canary window

session

GitHub-backed Post Plan sessions only

Final friend-sharing configuration

The checked-in implementation does not migrate or deploy the production service automatically. Follow the staged rollout below after registering the OAuth App and taking a D1 backup.

Related MCP server: MCP from API

Local development

Requirements: Node.js 20 or newer, a Cloudflare account containing the active boyan.live zone, Wrangler authentication, and a dedicated GitHub OAuth App with Device Flow enabled.

Create the OAuth App in GitHub's developer settings. Use the local or production Post Plan URL as its homepage, enable device flow, and do not request scopes. Device flow uses the app's client ID and does not require its client secret.

npm install
npx wrangler whoami

Create the D1 database once, then put its returned UUID in wrangler.jsonc. Copy the local variable template and set these values in .dev.vars:

npx wrangler d1 create post-plan
cp .dev.vars.example .dev.vars
# Required only while the checked-in AUTH_MODE is legacy or dual.
PUBLISH_TOKEN=replace-with-at-least-32-random-characters

# Public identifier of a dedicated GitHub OAuth App with device flow enabled.
GITHUB_CLIENT_ID=Iv1.replace-with-your-client-id

wrangler.jsonc contains the checked-in AUTH_MODE=dual and SESSION_TTL_DAYS=90 defaults. Change those Wrangler variables (or their environment-specific production equivalents) when testing a different rollout mode or session lifetime; do not duplicate them in .dev.vars.

Apply migrations, grant your GitHub account access in the local D1 database, and start the Worker:

npm run db:migrate:local
npm run auth:user -- grant YOUR_GITHUB_LOGIN --role owner --apply
npm run dev

In another terminal, authenticate the plugin against the local Worker. The command prints GitHub's verification URL and one-time user code, polls at GitHub's required interval, and stores only the returned Post Plan session in the mode-0600 config file.

node plugins/post-plan/scripts/configure.mjs \
  --url http://127.0.0.1:8787 \
  --login
node plugins/post-plan/scripts/configure.mjs --check

The local API is available at http://127.0.0.1:8787. A valid Post Plan session can also be supplied to automation as POST_PLAN_ACCESS_TOKEN; the old POST_PLAN_TOKEN name is accepted only during the compatibility release.

curl -fsS http://127.0.0.1:8787/healthz
jq -n --rawfile html examples/example-plan.html \
  '{title:"Example plan", html:$html, status:"active"}' | \
  curl -fsS http://127.0.0.1:8787/api/plans \
    -H "Authorization: Bearer $POST_PLAN_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    --data-binary @-

Production rollout

Treat this as a migration checklist. Do not switch directly from the shared token to session-only authentication.

  1. Register a dedicated production GitHub OAuth App, set its homepage to https://post-plan.boyan.live, enable device flow, and record its client ID. No client secret or requested scopes are needed.

  2. Export or otherwise back up the remote D1 database and record the current plan and revision counts.

  3. Configure GITHUB_CLIENT_ID, AUTH_MODE=dual, and SESSION_TTL_DAYS=90 as Worker variables. Keep the existing PUBLISH_TOKEN secret during the dual-auth canary.

  4. Run the checks and additive migration, then grant the owner and one friend by GitHub login. The management script resolves each login to the immutable GitHub numeric ID before storing the grant.

npm run check
npm run db:migrate:remote
npm run auth:user -- grant OWNER_GITHUB_LOGIN --role owner --apply --remote
npm run auth:user -- grant FRIEND_GITHUB_LOGIN --role publisher --apply --remote
npm run auth:user -- list --remote
npm run deploy
curl -fsS https://post-plan.boyan.live/healthz
  1. Have both accounts complete --login, publish, revise, and revoke separate canary plans. Confirm that each can mutate only their own plan and that existing public and historical URLs still render.

  2. Immediately before session-only cutover, backfill every legacy plan to the seeded owner and verify unchanged IDs, row counts, and zero null owners:

npm run auth:user -- backfill-owner OWNER_GITHUB_LOGIN --apply --remote
npm run auth:user -- verify --remote

The checked-in 0003 migration is deliberately additive. Create and apply the NOT NULL/owner-scoped idempotency rebuild only in a later release after these production checks pass.

  1. Change AUTH_MODE to session, deploy, verify the canaries again, and only then remove the shared secret:

npm run deploy
npx wrangler secret delete PUBLISH_TOKEN

If the canary fails before that later ownership-enforcement release, return to AUTH_MODE=dual and redeploy while keeping the additive identity tables. Do not rewrite ownership data as part of an application rollback. Restore the D1 backup only for a verified migration-integrity failure.

Invite management is intentionally operator-only:

# Add or restore a friend.
npm run auth:user -- grant FRIEND_GITHUB_LOGIN --role publisher --apply --remote

# Disable the account and all of its sessions. A stored login or numeric ID works.
npm run auth:user -- revoke FRIEND_GITHUB_LOGIN_OR_ID --apply --remote

# Audit current grants without changing them.
npm run auth:user -- list --remote

Omit --remote to manage the local Wrangler D1 database. Mutating commands require --apply, which makes accidental read-only invocations safe.

Friend install and login

An operator must grant the friend's GitHub login before they authenticate. A grant is tied to the account's immutable numeric GitHub ID, so a later username change does not transfer access to another person.

From a public checkout, install the Codex plugin:

git clone https://github.com/Johnnybyzhang/post-plan.git
cd post-plan

/Applications/Codex.app/Contents/Resources/codex \
  plugin marketplace add "$PWD"
/Applications/Codex.app/Contents/Resources/codex \
  plugin add post-plan@post-plan

Then sign in. --login opens or prints GitHub's verification page and user code; GitHub shows that the app requests no scopes. Post Plan writes its own session to ~/.config/post-plan/config.json with mode 0600 and never writes it into the checkout.

node plugins/post-plan/scripts/configure.mjs --login
node plugins/post-plan/scripts/configure.mjs --check

--check verifies the session with the service and shows the GitHub login snapshot and session expiry without printing the credential. Review and trust the bundled hook when Codex asks, restart Codex, use Default mode, and ask naturally for a plan—or invoke $post-plan:publish-plan explicitly.

Log out when the machine should no longer publish:

node plugins/post-plan/scripts/configure.mjs --logout
node plugins/post-plan/scripts/configure.mjs --check

Logout revokes the current Post Plan session; it does not revoke unrelated friends, change GitHub permissions, delete published plans, or make their unlisted viewer URLs private.

The public GitHub marketplace form is also supported:

/Applications/Codex.app/Contents/Resources/codex \
  plugin marketplace add Johnnybyzhang/post-plan
/Applications/Codex.app/Contents/Resources/codex \
  plugin add post-plan@post-plan

For plugin development, use the checkout-path form above so Codex reads the local plugin files.

API

All responses are JSON except public HTML pages. Authenticated API routes use Authorization: Bearer <POST_PLAN_ACCESS_TOKEN>. A legacy PUBLISH_TOKEN is accepted only when AUTH_MODE is legacy or dual.

Method

Route

Purpose

POST

/api/auth/device/start

Start GitHub device authorization; returns the verification URL, user code, expiry, and polling interval

POST

/api/auth/device/poll

Poll authorization and exchange an invited GitHub identity for a Post Plan session

GET

/api/me

Read the current login snapshot, role, and session expiry

DELETE

/api/sessions/current

Revoke the current Post Plan session

POST

/api/plans

Publish an owned plan; supports idempotency_key

GET

/api/plans/:id

Read the current plan as JSON

PATCH

/api/plans/:id

Publish a new revision of the caller's plan; supports if_revision

DELETE

/api/plans/:id

Revoke the caller's public plan

GET

/api/plans/:id/revisions

List revision metadata

GET

/p/:id

View the current public, unlisted plan

GET

/p/:id/r/:revision

View a historical public revision

GET

/healthz

Check Worker and D1 health

Device start and poll routes are unauthenticated but rate-limited. The Worker never returns or logs GitHub's temporary access token. A successful poll returns the Post Plan session once; only its hash and lifecycle metadata are retained in D1.

Create fields are title, html, summary, status, tags, codex_thread_id, repository, branch, and idempotency_key. html must be a complete document beginning with <!doctype html> and containing html and body elements.

The stable viewer route embeds the model document from /p/:id/content in an iframe without allow-same-origin. A response CSP blocks network connections, external scripts/styles, forms, nested frames, workers, and objects. Inline CSS and small inline presentation scripts are allowed inside the isolated document.

Stored identity data

Post Plan stores the GitHub account's immutable numeric ID, its current login as a display snapshot, the assigned Post Plan role and invite/revocation timestamps, and session metadata such as a token hash, session label, and issue/expiry/last-used/revocation times. It does not store the GitHub access token, GitHub email, private repository data, or organization membership. See the live /privacy and /terms pages before inviting friends.

The installed Codex desktop app registers the codex URL scheme. Existing local tasks use:

codex://threads/<uuid>

Codex injects the current exact thread as MCP request metadata at _meta.threadId. Shell-based fallbacks receive CODEX_THREAD_ID, while the plugin hook receives the root session_id. The public page uses a normal user-clicked anchor because browsers may ask for confirmation before opening Codex.

Cloud tasks use a separate web route, https://chatgpt.com/codex/tasks/<cloud-task-id>. A local thread UUID must not be inserted into that Cloud-task URL.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Johnnybyzhang/post-plan'

If you have feedback or need assistance with the MCP directory API, please join our Discord server