Learn with O.J.internal

Pulp Design Doc

Pulp

Mobile-to-Repo Commit Pipeline

Push content from your phone to any repo in two taps.


The Problem

When working in Claude on mobile, generated files (markdown docs, lead cards, design specs, content drafts) have no direct path to a git repository. The current workflow requires waiting for laptop access, downloading the file, moving it to the correct repo, committing, and pushing. This creates a bottleneck where useful outputs pile up in Claude conversations and never make it into version control.

The problem compounds across multiple repos (learnwithoj-internal, mathbliss, personal projects) that each need files committed to different directories.


The Solution

Pulp is a lightweight serverless API endpoint that accepts content via POST and commits it to the correct GitHub repo using the GitHub API. Paired with an iOS Shortcut, the workflow becomes: copy content from Claude, run the Shortcut, pick a repo, done.

Flow

Claude (phone) → Copy content → iOS Shortcut → Pulp API → GitHub API → Committed file

What Pulp Does

  1. Accepts a POST request with: content (the file body), filename, target repo, target directory, and an optional commit message
  2. Looks up the repo in its config to determine which GitHub account and token to use
  3. Commits the file to the specified path using the GitHub Contents API
  4. Returns the URL of the committed file

What Pulp Does NOT Do

  • No scraping, no browser automation, no OAuth dance on mobile
  • No file storage (content passes through to GitHub and is not retained)
  • No branch management in v1 (commits to default branch)

Architecture

Platform

Cloudflare Workers (recommended) or Vercel Edge Functions.

Rationale: zero cold starts, generous free tier (Cloudflare Workers: 100k requests/day free), edge deployment means low latency from anywhere, and secrets management built in for storing GitHub tokens.

Authentication

Pulp itself needs to be protected so random people can't commit to your repos. Two layers:

  • Bearer token: A secret token you generate and store in both the Worker secrets and the iOS Shortcut. Every request must include Authorization: Bearer <your-pulp-token>.
  • GitHub PAT: A single fine-grained personal access token scoped to only the repos Pulp needs, with contents: write permission. Stored as a Worker secret.

Config: Repo Registry

The repo registry maps repo identifiers (friendly names you'll see in the iOS Shortcut) to their GitHub coordinates and credentials.

{
  "repos": {
    "oj-internal": {
      "owner": "your-username",
      "repo": "learnwithoj-internal",
      "default_dir": "docs",
      "branch": "main"
    },
    "oj-leads": {
      "owner": "your-username",
      "repo": "learnwithoj-internal",
      "default_dir": "leads",
      "branch": "main"
    },
    "mathbliss": {
      "owner": "your-username",
      "repo": "mathbliss",
      "default_dir": "content",
      "branch": "main"
    },
    "another-project": {
      "owner": "your-username",
      "repo": "some-repo",
      "default_dir": "",
      "branch": "main"
    }
  }
}

Note: The GitHub PAT is stored as a single Cloudflare Worker secret (GITHUB_PAT), not in the config JSON. You can add a new repo to the registry by adding an entry to this config and redeploying (a one-line wrangler command). All repos are accessed through the same GitHub account.

The oj-leads entry is a good example of how you can have multiple shortcuts into the same repo targeting different directories. When Squeeze is built, its lead cards could use this same endpoint.

The two Claude accounts don't affect Pulp at all — both accounts generate content that you copy to your clipboard, and the iOS Shortcut doesn't care which app the clipboard content came from.


API Specification

Endpoint

POST https://pulp.<your-domain>.workers.dev/commit

Headers

Authorization: Bearer <pulp-token>
Content-Type: application/json

Request Body

{
  "repo": "oj-internal",
  "filename": "squeeze-design-doc.md",
  "content": "# Squeeze\n\nDesign doc content here...",
  "dir": "docs/tools",
  "message": "Add Squeeze design doc"
}
FieldRequiredDescription
repoYesKey from the repo registry (e.g., "oj-internal")
filenameYesName of the file to create or update
contentYesFile content (will be base64 encoded for GitHub API)
dirNoSubdirectory within the repo. Falls back to default_dir from config.
messageNoCommit message. Defaults to Add <filename> via Pulp

Response (Success)

{
  "status": "committed",
  "url": "https://github.com/owner/repo/blob/main/docs/tools/squeeze-design-doc.md",
  "sha": "abc123...",
  "repo": "oj-internal",
  "path": "docs/tools/squeeze-design-doc.md"
}

Response (Error)

{
  "status": "error",
  "message": "Repository 'unknown-repo' not found in registry"
}

Error Codes

HTTP StatusMeaning
401Missing or invalid Pulp bearer token
404Repo key not found in registry
409File already exists (see Update Behavior below)
422Missing required fields
502GitHub API error (token expired, permissions, etc.)

Update Behavior

If a file already exists at the target path, Pulp needs the file's current SHA to update it (GitHub API requirement). Two options:

  • v1 (simple): Return a 409 with the existing file's SHA. The iOS Shortcut can retry with the SHA to overwrite.
  • v2 (smart): Pulp automatically fetches the current SHA and overwrites. Add an overwrite: true flag to the request body.

Recommendation: Start with v2. You almost always want to update existing files, and the extra API call is negligible.


iOS Shortcut Design

The Shortcut is the user-facing interface. It should feel fast and require minimal input.

Quick Commit Flow (most common)

  1. Copy content from Claude
  2. Run Shortcut (from share sheet or home screen)
  3. Pick repo from a list menu (shows friendly names from registry)
  4. Enter filename (text input with smart default based on first line of content)
  5. Done — Shortcut POSTs to Pulp, shows success notification with GitHub link

Shortcut Actions

1. Get Clipboard
2. Show Menu: [oj-internal, oj-leads, mathbliss, personal-project]
3. Ask for Input: "Filename" (default: generated from clipboard first line)
4. Optional: Ask for Input: "Subdirectory" (default: use repo default)
5. Build JSON payload
6. POST to Pulp endpoint with Bearer token
7. Parse response
8. Show notification: "Committed to {repo} ✓" with link

Advanced: Paste + Commit Shortcut

For when you want to skip the repo selection because you always commit a certain type of content to the same place:

  • "Save Lead Card" — hardcoded to oj-leads, prompts only for filename
  • "Save Doc" — hardcoded to oj-internal/docs, prompts only for filename
  • "Save Math Post" — hardcoded to mathbliss/content, prompts only for filename

These are just duplicates of the main Shortcut with the repo pre-selected. Takes it from 5 taps to 3.


Cloudflare Worker Implementation

Project Structure

pulp/
  src/
    index.ts          # Request router and auth check
    commit.ts         # GitHub commit logic
    config.ts         # Repo registry
  wrangler.toml       # Cloudflare config
  package.json

Core Logic (commit.ts)

The commit flow against the GitHub Contents API:

1. Look up repo config from registry
2. Retrieve the GitHub PAT from Worker secrets
3. Build the file path: {dir}/{filename}
4. Check if file exists (GET /repos/{owner}/{repo}/contents/{path})
   - If exists: capture SHA for update
   - If not: proceed with create
5. PUT /repos/{owner}/{repo}/contents/{path}
   - body: { message, content (base64), sha (if update), branch }
6. Return the committed file URL and SHA

GitHub API Calls

Check if file exists:

GET https://api.github.com/repos/{owner}/{repo}/contents/{path}?ref={branch}
Headers: Authorization: Bearer {github-pat}

Create or update file:

PUT https://api.github.com/repos/{owner}/{repo}/contents/{path}
Headers: Authorization: Bearer {github-pat}
Body: {
  "message": "Add squeeze-design-doc.md via Pulp",
  "content": "<base64-encoded-content>",
  "branch": "main",
  "sha": "<existing-sha-if-updating>"
}

Secrets (via wrangler secret)

PULP_TOKEN   # Your bearer token for authenticating to Pulp
GITHUB_PAT   # Fine-grained PAT for your GitHub account

GitHub PAT Setup

Creating a Fine-Grained Token

  1. Go to Settings > Developer Settings > Personal Access Tokens > Fine-grained tokens
  2. Name: "Pulp"
  3. Expiration: 90 days (set a calendar reminder to rotate)
  4. Repository access: select only the repos Pulp needs
  5. Permissions: Contents (Read and write) — this is the only permission needed
  6. Generate and save to Cloudflare Worker secrets via wrangler secret put GITHUB_PAT

Why Fine-Grained Over Classic

Fine-grained tokens are scoped to specific repos with specific permissions. If the token leaks, the blast radius is limited to just file contents in the repos you selected, not your entire GitHub account. Classic tokens give broad access and are not recommended.


Security Considerations

  • Bearer token on Pulp: Without this, anyone who discovers your Worker URL can commit to your repos. Generate a strong random token (32+ chars).
  • HTTPS only: Cloudflare Workers are HTTPS by default. Content in transit is encrypted.
  • No content retention: Pulp passes content through to GitHub and does not store, log, or cache file contents. The only logs are Cloudflare's default request logs (URL, status code, timing) which do not include request bodies.
  • Token rotation: Fine-grained PATs expire. Set 90-day expiration and rotate. Update the Worker secret with wrangler secret put GITHUB_PAT.
  • Rate limits: GitHub API allows 5,000 requests/hour per authenticated user. You will never hit this with manual mobile commits.

Implementation Phases

Phase 1: Core Endpoint

Build the Cloudflare Worker with single-repo support. Hardcode learnwithoj-internal as the only repo. Test with curl from your terminal. Estimated time: one session.

Phase 2: Multi-Repo Registry

Add the repo registry config. Support switching between repos and target directories. Test all configured repos. Estimated time: one session.

Phase 3: iOS Shortcut

Build the main Shortcut with repo picker menu. Build the specialized shortcuts (Save Lead Card, Save Doc, etc.). Test the full flow from Claude on phone to committed file. Estimated time: one session.

Phase 4: Update Support

Add the auto-fetch SHA logic for updating existing files. Add the overwrite: true flag. Test update scenarios. Estimated time: quick addition.

Phase 5: Quality of Life

  • Add a GET /repos endpoint that returns the registry list (so the iOS Shortcut can dynamically build the menu instead of hardcoding repo names)
  • Add optional Markdown frontmatter injection (auto-add created_at, source: pulp to files)
  • Add a GET /recent endpoint that shows last 10 commits made through Pulp (for quick verification)

Integration with Squeeze

When Squeeze is built, Pulp becomes its commit layer. Instead of Squeeze having its own git logic, it can POST lead cards to Pulp's endpoint. This means the Squeeze CLI on your laptop and the mobile capture flow both use the same commit path, and lead cards end up in the same place regardless of which device you're on.

The oj-leads repo entry in the registry is pre-configured for this. Squeeze just needs to POST to /commit with repo: "oj-leads" and the lead card content.


Naming Note

Working name is Pulp — push the pulp of the content to where it belongs. Fits the citrus brand alongside Squeeze and Fresh Squeeze. Open to renaming.


Open Questions

  • Should Pulp support creating directories that do not exist yet, or require the directory to already exist in the repo?
  • Is there value in a "dry run" mode that shows what would be committed without actually committing? Useful for debugging the Shortcut.
  • Should the iOS Shortcut support pasting images (screenshots, diagrams) or just text? GitHub Contents API supports binary files via base64 but the Shortcut gets more complex.
  • What is the commit author strategy? The PAT's associated GitHub user will be the author. Is that fine, or should commits show a different name (e.g., "Pulp Bot") to distinguish mobile commits from laptop commits?
  • Should Pulp eventually support branch creation for PRs instead of direct commits to main? Useful if you want review before merge.
  • Is there a need for a "bulk commit" mode where you paste multiple files at once (e.g., a design doc plus its associated lead card)?
  • Token rotation reminders: build into the tool or handle externally (calendar reminder, etc.)?

Pulp | Learn with O.J. | learnwithoj.com