pike/glyph Public Public: anyone can read this; nobody can change it.
forked from apps/glyph
workbenchthis machine
Files acd87b6b6146da11e0c9272c92a02a8e707adf4a
| 1 | # glyph |
| 2 | |
| 3 | Type a prompt, get SVG logos and icons drawn by Claude; preview in your theme, copy or download. |
| 4 | |
| 5 | This is an app: the forge runs it. Production follows the default branch; |
| 6 | every open pull gets a preview at its own address with its own copy of the |
| 7 | data. Everything the app keeps lives under `FORGE_APP_DATA`, which is what |
| 8 | makes "production data" and "preview data" two different things. |
| 9 | |
| 10 | sh |
| 11 | PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts # run it here |
| 12 | bun test # what `forge.json`'s app.test runs on every push |
| 13 | FORGE_APP_DATA=./data bun run src/tty.ts # the terminal |
| 14 | FORGE_APP_DATA=./data bun run src/db.ts --check # schema version, integrity |
| 15 | bun run lint:design # no colour but the tokens' |
| 16 | bun run contract:emit # rewrite contract/openapi.json from the routes |
| 17 | |
| 18 | |
| 19 | `app.test` in `forge.json` runs beside `app.run` in every deploy, not before |
| 20 | it: the preview comes up the moment the app answers, and says `live`, or |
| 21 | `serving · tests failed` the moment the suite disagrees. |
| 22 | |
| 23 | ## The shape |
| 24 | |
| 25 | One implementation, many doors. `src/actions.ts` is every operation as a |
| 26 | plain function — `(db, who, input)` in, data or a named failure out — and |
| 27 | the page, the JSON API, the MCP server and the terminal all call it and |
| 28 | decide nothing themselves. `AGENTS.md` says how to add to it. |
| 29 | |
| 30 | | file | what it is | |
| 31 | | ---------------------------------- | -------------------------------------------------------------- | |
| 32 | | `src/actions.ts` | list, add, delete; the audit trail, read | |
| 33 | | `contract.json` | who may do what: roles, and the least role each verb needs | |
| 34 | | `src/contract.ts`, `src/can.ts` | the table, read at boot; the forge's evaluator, verbatim | |
| 35 | | `src/security.ts` | the door: rationing, same origin, the table before the body | |
| 36 | | `src/audit.ts` | every change and every refused one, as a row | |
| 37 | | `src/who.ts` | who is asking, from the forge's signed headers | |
| 38 | | `src/db.ts` | SQLite under `FORGE_APP_DATA`, WAL, forward-only migrations | |
| 39 | | `src/live.ts` | the in-process bus and `GET /events` | |
| 40 | | `src/mcp.ts` | the actions as MCP tools at `POST /mcp` | |
| 41 | | `src/routes.ts` | every path, in one table, each API route described beside it | |
| 42 | | `src/openapi.ts` | the routes as OpenAPI; committed at `contract/openapi.json` | |
| 43 | | `src/page.ts` | the HTML, rendered on the server, with its share metadata | |
| 44 | | `design/tokens.json`, `src/tokens.ts` | the look as data, light and dark; compiled to `/tokens.css` | |
| 45 | | `src/http.ts` | the floor: headers, body cap, files | |
| 46 | | `src/server.ts` | boot: open the database, serve | |
| 47 | | `src/tty.ts` | the terminal, over the same actions | |
| 48 | | `src/env.ts` | what the forge told this process | |
| 49 | | `static/app.css`, `static/live.js` | the look in the tokens' words, and the list that stays current | |
| 50 | |
| 51 | ## What every app stands on |
| 52 | |
| 53 | - **The door.** Before a route sees a request, `src/security.ts` rations |
| 54 | writes (and a stranger's API calls), refuses a write from another site, |
| 55 | and turns away — before the body is read — anyone the table lets through |
| 56 | a resource's door for no row at all: 401 for a stranger, 403 for anyone |
| 57 | else. The action still decides about the real row. |
| 58 | - **The audit trail.** Every change, and every change refused, is a row: |
| 59 | who, which resource and verb, which row, when, and whether they were |
| 60 | let. Admins read it at `GET /api/audit`. A row that cannot be written |
| 61 | is a log line, never a failed request. |
| 62 | - **The API, described.** `GET /openapi.json` is `contract/openapi.json`, |
| 63 | written from the routes by `bun run contract:emit`; each operation names |
| 64 | its cell of the table (`x-contract`). `bun test` fails while it is stale. |
| 65 | - **Up, or not.** `GET /healthz` answers `{ ok, version, uptime }`; the |
| 66 | forge waits on it before it sends anyone to a deploy. |
| 67 | - **The look.** `design/tokens.json` holds every colour, space and size, |
| 68 | light and dark; nothing else spells out a colour (`bun run lint:design`), |
| 69 | and every pair a person reads meets 4.5:1 in both themes. |
| 70 | - **The open web.** A shared link unfurls (description, Open Graph, a |
| 71 | card); `/robots.txt` keeps crawlers to the page, and out of previews |
| 72 | entirely; `/.well-known/security.txt` sends a reporter to the forge |
| 73 | that runs the app. |
| 74 | |
| 75 | ## Who you are |
| 76 | |
| 77 | The forge says who is asking on every request it proxies, and the app |
| 78 | verifies the signature (`src/who.ts`); nothing else is trusted. Three tiers: |
| 79 | |
| 80 | - **anon** — anyone. Reads. |
| 81 | - **user** — signed in to the forge and able to see this app. Adds notes, as |
| 82 | themselves, and removes their own. |
| 83 | - **admin** — able to write this repository. Removes any note. |
| 84 | |
| 85 | "Sign in" is `/__forge/signin?to=<path>` and "Sign out" is |
| 86 | `/__forge/signout`; the forge answers both on the app's own host, and |
| 87 | `/__forge/report` too: it sends anyone, with no account, to report a |
| 88 | problem on this app's repository, if that repository is public. |
| 89 | |
| 90 | ## Any interface |
| 91 | |
| 92 | The same actions, the same rules, at every door. A script is the person |
| 93 | whose forge grant it carries; a bare script is anonymous. |
| 94 | |
| 95 | sh |
| 96 | curl https://<app>/api/notes |
| 97 | curl -X POST https://<app>/api/notes -H 'content-type: application/json' -d '{"body":"hi"}' |
| 98 | curl -X DELETE https://<app>/api/notes/1 |
| 99 | |
| 100 | |
| 101 | As an MCP server, for Claude Code or Claude Desktop — `list_notes`, |
| 102 | `add_note`, `delete_note`: |
| 103 | |
| 104 | sh |
| 105 | claude mcp add --transport http __TITLE__ https://<app>/mcp |
| 106 | |
| 107 | |
| 108 | json |
| 109 | { |
| 110 | "mcpServers": { "__TITLE__": { "type": "http", "url": "https://<app>/mcp" } } |
| 111 | } |
| 112 | |
| 113 | |
| 114 | The page stays current over `/events`: a note someone else adds or removes |
| 115 | appears without a reload. Without JavaScript it is the same page, one |
| 116 | reload behind. |
| 117 | |
| 118 | The terminal opens with the exact command in `FORGE_APP_SSH` when the app |
| 119 | runs (`ssh -p 2222 <name>@<forge>` — the app is the login name, the forge |
| 120 | the host; `<name>--<pull>@<forge>` for a preview's). It is `src/tty.ts` on |
| 121 | a real pty over the same data, as the account at the keyboard. |
| 122 | |
| 123 | The forge sets `PORT`, `FORGE_APP_DATA`, `FORGE_APP_ENV` (`production` or |
| 124 | `preview`), `FORGE_APP_URL`, `FORGE_APP_COMMIT`, `FORGE_APP_SSH` and |
| 125 | `FORGE_APP_IDENTITY_KEY` when it starts the app. `forge.json` says how. |