wren/glyph Public Public: anyone can read this; nobody can change it.

forked from apps/glyph

workbenchthis machine
Files acd87b6b6146da11e0c9272c92a02a8e707adf4a
125 lines · 6.7 KB · ~1.7k tok Raw History
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.
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts