{"id":51340834,"url":"https://github.com/punt-labs/postern","last_synced_at":"2026-07-02T07:30:50.194Z","repository":{"id":350713918,"uuid":"1207325306","full_name":"punt-labs/postern","owner":"punt-labs","description":"Drive a live Pharo image from Claude Code, Codex or any HTTP client — full development cycle, no GUI required.","archived":false,"fork":false,"pushed_at":"2026-06-11T17:19:21.000Z","size":399,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T19:12:07.161Z","etag":null,"topics":["claude-code","codex-cli","pharo","pharo-smalltalk","smalltalk"],"latest_commit_sha":null,"homepage":"https://github.com/punt-labs/postern","language":"Smalltalk","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/punt-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-10T20:20:57.000Z","updated_at":"2026-06-11T17:19:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/punt-labs/postern","commit_stats":null,"previous_names":["punt-labs/postern"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/punt-labs/postern","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punt-labs%2Fpostern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punt-labs%2Fpostern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punt-labs%2Fpostern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punt-labs%2Fpostern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/punt-labs","download_url":"https://codeload.github.com/punt-labs/postern/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/punt-labs%2Fpostern/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35038239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","codex-cli","pharo","pharo-smalltalk","smalltalk"],"created_at":"2026-07-02T07:30:49.274Z","updated_at":"2026-07-02T07:30:50.183Z","avatar_url":"https://github.com/punt-labs.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Postern\n\n\u003e Drive a live Pharo image over HTTP from a coding agent or shell.\n\n[![License](https://img.shields.io/github/license/punt-labs/postern)](LICENSE)\n[![Lint](https://img.shields.io/github/actions/workflow/status/punt-labs/postern/lint.yml?label=Lint)](https://github.com/punt-labs/postern/actions/workflows/lint.yml)\n[![Test](https://img.shields.io/github/actions/workflow/status/punt-labs/postern/test.yml?label=Test)](https://github.com/punt-labs/postern/actions/workflows/test.yml)\n[![Docs](https://img.shields.io/github/actions/workflow/status/punt-labs/postern/docs.yml?label=Docs)](https://github.com/punt-labs/postern/actions/workflows/docs.yml)\n[![Pharo 12](https://img.shields.io/badge/Pharo-12-%23aac9ff.svg)](https://pharo.org/download)\n\nPostern exposes a running Pharo image as an HTTP server. Clients send\nSmalltalk expressions to `/repl`, read `/help` from the live image, and\ndrive compile, test, inspect, and commit loops without going through the\nPharo GUI. It is intended for local development, CI, and agent-driven\nworkflows where the image itself is the runtime.\n\n**Platforms:** macOS, Linux (`x86_64`, `arm64`/`aarch64`), Windows (`x86_64`; ARM64 via x86_64 Stack VM emulation, headless-only) (Pharo 12)\n\n## Quick Start\n\n```bash\ngit clone https://github.com/punt-labs/postern.git\ncd postern\nmake\nmake setup\nmake start\n```\n\n`make setup` downloads the matching Pharo image and VM for the current\nhost. If Pharo cannot write to its usual user config directories\n(for example inside a Codex or Claude Code sandbox), Postern falls back\nto a repo-local runtime home under `.tmp/pharo-home`.\n\nOn Windows ARM64, `make setup` automatically uses the Windows `x86_64`\nStack VM under emulation because the default Windows JIT VM is not\nreliable there. That path is headless-only, so use `make start-headless`\ninstead of `make start`.\n\nOn headless Linux sessions without `DISPLAY` or `WAYLAND_DISPLAY`, use\n`make start-headless`.\n\nIf port `8422` is already in use, override it on the command line, for\nexample `make PORT=8432 start-headless`.\n\n```bash\ncurl -s http://localhost:8422/health\ncurl -s http://localhost:8422/help\n```\n\nBefore exposing Postern beyond your machine or handing it to an agent,\nread [Security](#security).\n\n## Features\n\n- **HTTP REPL** — Evaluate Smalltalk in a live image through `POST /repl`.\n- **Live help from the image** — `/help` documents the loaded packages,\n  conventions, and workflow directly from the running image.\n- **GUI and headless startup** — `make start` launches the Pharo UI when\n  the selected VM supports it, and `make start-headless` runs without\n  it. Windows ARM64 currently uses a headless-only Stack VM path.\n- **Dashboard** — `PosternDashboard` shows live request traffic, status,\n  and request and response bodies.\n- **Iceberg sync helper** — Repairs Iceberg reference-commit drift after\n  CLI Git operations.\n- **Optional token auth** — Loopback mode can run without auth; public\n  binding always requires a token.\n\n## What It Looks Like\n\n```text\n$ curl -s http://localhost:8422/health\nok\n\n$ make status\nalive -- \u003cclass-count\u003e Postern classes loaded\n\n$ make test\nTests: \u003crun-count\u003e  Passed: \u003cpass-count\u003e  Failures: 0  Errors: 0\n```\n\n![Postern Dashboard showing live `/repl` activity, including the test run driven through Postern](docs/images/postern-dashboard.png)\n\n*The dashboard after driving Postern through `make test` and related\n`/repl` activity.*\n\n## API\n\n### Eval Server\n\n| Route | Method | Description |\n|-------|--------|-------------|\n| `/repl` | `POST` | Evaluate Smalltalk from a `text/plain` body and return the `printString` of the result. |\n| `/health` | `GET` | Return `ok`. No auth required. |\n| `/help` | `GET` | Return the table of contents for the live help. No auth required. |\n| `/help/{section}` | `GET` | Return a specific help section: `api`, `browse`, `pharo`, `dispatch`, `lint`, `git`, `testing`, `safety`, `makefile`, or `lessons`. |\n\n#### Binding and Authentication\n\n| Method | Binding | Auth |\n|--------|---------|------|\n| `PosternServer startOn: 8422` | loopback | none |\n| `PosternServer startOn: 8422 withAuth: true` | loopback | required |\n| `PosternServer startPublicOn: 8422` | all interfaces | required |\n\nThere is no unauthenticated public mode. When auth is enabled, `/repl`\nrequires an `X-Eval-Token` header. The token is written to\n`.tmp/eval-token`, and clients should read that file and send the header\nexplicitly. `/health` and `/help` are always unauthenticated.\n\nSend LF line endings in requests. The server converts them to CR before\npassing source to the Pharo compiler. `/help` responses use LF.\n\n### Dashboard\n\nOpen from the World menu: **Postern \u003e Dashboard**, or evaluate:\n\n```smalltalk\nPosternDashboard open.\n```\n\nThe dashboard shows live traffic per server: method, path, status code,\nduration, and body snippet. The filter field takes keyboard focus on\nopen. Multiple windows can monitor different servers simultaneously.\n\n### Iceberg Sync\n\nAfter CLI Git operations, Iceberg's working copy reference commit can\ndrift from filesystem `HEAD`, showing a false dirty state. Fix it from\nthe Iceberg browser with **Postern \u003e Sync reference commit**, or\nevaluate:\n\n```smalltalk\nrepo workingCopy referenceCommit: repo head commit.\n```\n\n## Setup\n\n### Load into Another Image\n\n```smalltalk\nMetacello new\n  baseline: 'Postern';\n  repository: 'github://punt-labs/postern:main';\n  load.\n```\n\nLoad including tests:\n\n```smalltalk\nMetacello new\n  baseline: 'Postern';\n  repository: 'github://punt-labs/postern:main';\n  load: 'all'.\n```\n\n### Agent Bootstrap\n\nExternal agents should read the live help before writing Smalltalk:\n\n```bash\ncurl -s http://localhost:8422/help\ncurl -s http://localhost:8422/help/api\ncurl -s http://localhost:8422/help/browse\ncurl -s http://localhost:8422/help/pharo\ncurl -s http://localhost:8422/help/safety\n```\n\nThe `/help` endpoint is served by the running image, so it reflects the\nloaded packages and current conventions instead of a static doc set.\n\n| Section | What it documents |\n|---------|-------------------|\n| `/help/api` | Eval server protocol: endpoints, auth, request and response format |\n| `/help/browse` | Read-only introspection: classes, methods, senders, implementors, source lookup |\n| `/help/pharo` | Pharo 12 standards: fluid class syntax, protocols, JSON pattern |\n| `/help/dispatch` | Sub-agent delegation: TDD cycle, required spec elements, anti-patterns |\n| `/help/lint` | Lint discipline: `make lint` is the gate, not per-method critiques |\n| `/help/git` | Iceberg commits, CLI merges, reference commit sync |\n| `/help/testing` | Scoped test runs, never the full Pharo suite |\n| `/help/safety` | Deadlock prevention, image discipline, orphan recovery |\n| `/help/makefile` | Image lifecycle: setup, start, stop, rebuild |\n| `/help/lessons` | Incident record: cascades, process failures, scope safety |\n\nFor Codex users, this repo also includes `.codex/config.toml` and\n`codex/rules/default.rules` so localhost help and REPL access can be\npreconfigured after the repo is trusted once. `make setup` already\nhandles the common sandbox case by falling back to `.tmp/pharo-home`\nwhen the normal Pharo config directories are not writable.\n\n## Security\n\nPostern accepts Smalltalk code over HTTP and runs it in the live image.\nA client that can successfully call `/repl` can do what normal Smalltalk\ncode in that image can do: change classes and methods, inspect objects,\nread and write files, and run shell commands with the OS permissions of\nthe user running Pharo.\n\nBy default, `make start` and `make start-headless` bind to `localhost`\nonly and do **not** enable auth. When auth is enabled, the\n`X-Eval-Token` is a shared secret for `/repl`; it is not a sandbox,\npermission system, or read-only mode. `/help` and `/health` remain\nunauthenticated even when auth is on.\n\nDo not expose Postern to untrusted networks or untrusted agents. Do not\nrun it in an environment containing secrets or data you would not hand\nto arbitrary code running as your user account.\n\n## Live Image Model\n\nPharo stores code in a live object graph called an image. Compiling a\nmethod installs it into that graph immediately. The same session can\ndefine a class, call a method on it, run its tests, and inspect the\nobjects it creates without a process restart.\n\nFor agent-driven coding, that means the loop stays inside the running\nsystem. After sending a compile request, the agent can call the new\nmethod in the next request and branch on the real runtime result, not a\nrestarted subprocess. Iceberg runs inside the same image, so commit\noperations can stay in the same session too.\n\n## Agent Workflow\n\nAn external agent can drive a complete edit cycle through HTTP: read the\nlive help, define or compile code, run scoped tests, and commit through\nIceberg. A typical sequence looks like this:\n\n```bash\ncurl -s http://localhost:8422/help/api\ncurl -s http://localhost:8422/help/pharo\n```\n\n```bash\ncurl -s -X POST http://localhost:8422/repl \\\n  -H \"Content-Type: text/plain\" \\\n  -d \"(Object \u003c\u003c #Counter\n  slots: { #count };\n  package: 'MyPackage') install\"\n```\n\n```bash\ncurl -s -X POST http://localhost:8422/repl \\\n  -H \"Content-Type: text/plain\" \\\n  -d \"Counter compile: 'increment\n  count := (count ifNil: [ 0 ]) + 1' classified: 'actions'\"\n```\n\n```bash\ncurl -s -X POST http://localhost:8422/repl \\\n  -H \"Content-Type: text/plain\" \\\n  -d \"CounterTest buildSuite run\"\n```\n\n```bash\ncurl -s -X POST http://localhost:8422/repl \\\n  -H \"Content-Type: text/plain\" \\\n  -d \"| repo |\nrepo := IceRepository registry detect: [ :r | r name = 'my-project' ].\nrepo workingCopy refreshDirtyPackages.\nrepo workingCopy commitWithMessage: 'feat(counter): add increment'\"\n```\n\n## Packages\n\n| Package | Contents |\n|---------|----------|\n| `Postern-Core` | `PosternServer`, `PosternDelegate`, `PosternHelp`, `PosternImageBrowser`, `PosternWidget` |\n| `Postern-Dashboard` | `PosternDashboard`, `PosternRequestLogger`, `PosternDashboardModel` |\n| `Postern-IcebergExtensions` | `PosternSyncReferenceCommitCommand` for Iceberg reference-commit sync |\n\n## Trivia\n\nThe name is intentional: a postern is a back door or gate, a private\nside entrance, or in fortification usage, a small secondary gate in a\nwall or castle. That fits this project literally: it gives tools a side\nentrance into a live Pharo image without making the GUI the only way in.\n\n## Development\n\n```bash\nmake rebuild     # fresh image from Tonel (proves source completeness)\nmake test        # run all Postern tests\nmake lint        # Renraku lint — zero non-clean lines required before commit\nmake filein      # reload Tonel packages into a running image\nmake eval        # interactive Smalltalk eval (stdin to eval server)\nmake transcript  # read the Pharo Transcript\nmake status      # health check and loaded-class count\nmake stop        # kill Pharo without saving the image\n```\n\nThe image is disposable. All code lives in `src/` in Tonel format.\n`make rebuild` must always succeed; if it fails, the source files are\nincomplete.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpunt-labs%2Fpostern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpunt-labs%2Fpostern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpunt-labs%2Fpostern/lists"}