snatching amp's walkthrough for my own purposes mwhahaha traverse.dunkirk.sh/diagram/6121f05c-a5ef-4ecf-8ffc-02534c5e767c

feat: add og images

dunkirk.sh 0e83eb16 b3d85b32

verified
+273 -3
+49
bun.lock
··· 5 5 "": { 6 6 "name": "traverse", 7 7 "dependencies": { 8 + "@fontsource/inter": "^5.2.8", 8 9 "@modelcontextprotocol/sdk": "^1.26.0", 10 + "@resvg/resvg-wasm": "^2.6.2", 11 + "satori": "^0.19.1", 9 12 }, 10 13 "devDependencies": { 11 14 "@types/bun": "latest", ··· 16 19 }, 17 20 }, 18 21 "packages": { 22 + "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], 23 + 19 24 "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], 20 25 21 26 "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], 22 27 28 + "@resvg/resvg-wasm": ["@resvg/resvg-wasm@2.6.2", "", {}, "sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw=="], 29 + 30 + "@shuding/opentype.js": ["@shuding/opentype.js@1.4.0-beta.0", "", { "dependencies": { "fflate": "^0.7.3", "string.prototype.codepointat": "^0.2.1" }, "bin": { "ot": "bin/ot" } }, "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA=="], 31 + 23 32 "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], 24 33 25 34 "@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="], ··· 30 39 31 40 "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], 32 41 42 + "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], 43 + 33 44 "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], 34 45 35 46 "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], ··· 40 51 41 52 "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 42 53 54 + "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], 55 + 56 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 57 + 43 58 "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], 44 59 45 60 "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], ··· 51 66 "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], 52 67 53 68 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 69 + 70 + "css-background-parser": ["css-background-parser@0.1.0", "", {}, "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="], 71 + 72 + "css-box-shadow": ["css-box-shadow@1.0.0-3", "", {}, "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="], 73 + 74 + "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], 75 + 76 + "css-gradient-parser": ["css-gradient-parser@0.0.17", "", {}, "sha512-w2Xy9UMMwlKtou0vlRnXvWglPAceXCTtcmVSo8ZBUvqCV5aXEFP/PC6d+I464810I9FT++UACwTD5511bmGPUg=="], 77 + 78 + "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], 54 79 55 80 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 56 81 ··· 60 85 61 86 "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 62 87 88 + "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], 89 + 63 90 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], 64 91 65 92 "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], ··· 84 111 85 112 "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], 86 113 114 + "fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="], 115 + 87 116 "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], 88 117 89 118 "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], ··· 101 130 "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 102 131 103 132 "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 133 + 134 + "hex-rgb": ["hex-rgb@4.3.0", "", {}, "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw=="], 104 135 105 136 "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], 106 137 ··· 124 155 125 156 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], 126 157 158 + "linebreak": ["linebreak@1.1.0", "", { "dependencies": { "base64-js": "0.0.8", "unicode-trie": "^2.0.0" } }, "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ=="], 159 + 127 160 "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 128 161 129 162 "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], ··· 145 178 "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], 146 179 147 180 "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 181 + 182 + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], 183 + 184 + "parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="], 148 185 149 186 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], 150 187 ··· 154 191 155 192 "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], 156 193 194 + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], 195 + 157 196 "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], 158 197 159 198 "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], ··· 167 206 "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], 168 207 169 208 "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 209 + 210 + "satori": ["satori@0.19.1", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-/XaT/JiWLfNlgjlQdde4wXB1/6F+FEze9c3OW2QIH0ywsfOrY57YOetgESWyOFHW3JfEQ6dJAo2U9Xwb7+DDAw=="], 170 211 171 212 "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], 172 213 ··· 188 229 189 230 "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], 190 231 232 + "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], 233 + 234 + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], 235 + 191 236 "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 192 237 193 238 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], ··· 196 241 197 242 "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 198 243 244 + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], 245 + 199 246 "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], 200 247 201 248 "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], ··· 203 250 "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 204 251 205 252 "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 253 + 254 + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], 206 255 207 256 "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 208 257
+4 -1
package.json
··· 23 23 "typescript": "^5" 24 24 }, 25 25 "dependencies": { 26 - "@modelcontextprotocol/sdk": "^1.26.0" 26 + "@fontsource/inter": "^5.2.8", 27 + "@modelcontextprotocol/sdk": "^1.26.0", 28 + "@resvg/resvg-wasm": "^2.6.2", 29 + "satori": "^0.19.1" 27 30 } 28 31 }
+21
src/index.ts
··· 4 4 import { generateViewerHTML } from "./template.ts"; 5 5 import type { WalkthroughDiagram } from "./types.ts"; 6 6 import { initDb, loadAllDiagrams, saveDiagram, deleteDiagramFromDb, generateId, getSharedUrl, saveSharedUrl } from "./storage.ts"; 7 + import { generateOgImage } from "./og.ts"; 7 8 import { loadConfig } from "./config.ts"; 8 9 9 10 const PORT = parseInt(process.env.TRAVERSE_PORT || "4173", 10); ··· 25 26 port: PORT, 26 27 async fetch(req) { 27 28 const url = new URL(req.url); 29 + 30 + // OG image route — must be matched before /diagram/:id 31 + const ogMatch = url.pathname.match(/^\/diagram\/([\w-]+)\/og\.png$/); 32 + if (ogMatch) { 33 + const id = ogMatch[1]!; 34 + const diagram = diagrams.get(id); 35 + if (!diagram) { 36 + return new Response("Not found", { status: 404 }); 37 + } 38 + const nodeNames = Object.values(diagram.nodes).map(n => n.title); 39 + const png = await generateOgImage(id, diagram.summary, nodeNames); 40 + return new Response(png, { 41 + headers: { 42 + "Content-Type": "image/png", 43 + "Cache-Control": "public, max-age=86400", 44 + }, 45 + }); 46 + } 47 + 28 48 const diagramMatch = url.pathname.match(/^\/diagram\/([\w-]+)$/); 29 49 30 50 if (diagramMatch) { ··· 42 62 shareServerUrl: config.shareServerUrl, 43 63 diagramId: id, 44 64 existingShareUrl: existingShareUrl ?? undefined, 65 + baseUrl: url.origin, 45 66 }), { 46 67 headers: { "Content-Type": "text/html; charset=utf-8" }, 47 68 });
+189
src/og.ts
··· 1 + import satori from "satori"; 2 + import { initWasm, Resvg } from "@resvg/resvg-wasm"; 3 + import { join } from "path"; 4 + 5 + // Load Inter font files (woff, not woff2 — satori doesn't support woff2) 6 + const fontsDir = join(import.meta.dir, "../node_modules/@fontsource/inter/files"); 7 + const [interRegular, interBold] = await Promise.all([ 8 + Bun.file(join(fontsDir, "inter-latin-400-normal.woff")).arrayBuffer(), 9 + Bun.file(join(fontsDir, "inter-latin-700-normal.woff")).arrayBuffer(), 10 + ]); 11 + 12 + // Initialize resvg-wasm 13 + const wasmPath = join(import.meta.dir, "../node_modules/@resvg/resvg-wasm/index_bg.wasm"); 14 + await initWasm(Bun.file(wasmPath).arrayBuffer()); 15 + 16 + // Cache generated images by diagram ID 17 + const cache = new Map<string, Buffer>(); 18 + 19 + export async function generateOgImage( 20 + id: string, 21 + summary: string, 22 + nodeNames: string[], 23 + ): Promise<Buffer> { 24 + const cached = cache.get(id); 25 + if (cached) return cached; 26 + 27 + const nodeCount = nodeNames.length; 28 + const displayNodes = nodeNames.slice(0, 8); 29 + const extra = nodeCount > 8 ? nodeCount - 8 : 0; 30 + 31 + const svg = await satori( 32 + { 33 + type: "div", 34 + props: { 35 + style: { 36 + width: "100%", 37 + height: "100%", 38 + display: "flex", 39 + flexDirection: "column", 40 + justifyContent: "space-between", 41 + padding: "60px", 42 + backgroundColor: "#0a0a0a", 43 + color: "#e5e5e5", 44 + fontFamily: "Inter", 45 + }, 46 + children: [ 47 + // Top: Traverse label 48 + { 49 + type: "div", 50 + props: { 51 + style: { 52 + display: "flex", 53 + alignItems: "center", 54 + gap: "10px", 55 + }, 56 + children: [ 57 + { 58 + type: "div", 59 + props: { 60 + style: { 61 + fontSize: "20px", 62 + fontWeight: 700, 63 + color: "#a3a3a3", 64 + letterSpacing: "0.05em", 65 + textTransform: "uppercase" as const, 66 + }, 67 + children: "Traverse", 68 + }, 69 + }, 70 + ], 71 + }, 72 + }, 73 + // Middle: Summary headline 74 + { 75 + type: "div", 76 + props: { 77 + style: { 78 + display: "flex", 79 + flexDirection: "column", 80 + gap: "24px", 81 + flex: 1, 82 + justifyContent: "center", 83 + }, 84 + children: [ 85 + { 86 + type: "div", 87 + props: { 88 + style: { 89 + fontSize: summary.length > 60 ? "36px" : "44px", 90 + fontWeight: 700, 91 + lineHeight: 1.2, 92 + color: "#e5e5e5", 93 + overflow: "hidden", 94 + textOverflow: "ellipsis", 95 + }, 96 + children: summary, 97 + }, 98 + }, 99 + ], 100 + }, 101 + }, 102 + // Bottom: Node pills + count 103 + { 104 + type: "div", 105 + props: { 106 + style: { 107 + display: "flex", 108 + alignItems: "center", 109 + justifyContent: "space-between", 110 + gap: "16px", 111 + }, 112 + children: [ 113 + { 114 + type: "div", 115 + props: { 116 + style: { 117 + display: "flex", 118 + flexWrap: "wrap", 119 + gap: "8px", 120 + flex: 1, 121 + }, 122 + children: [ 123 + ...displayNodes.map((name) => ({ 124 + type: "div", 125 + props: { 126 + style: { 127 + fontSize: "14px", 128 + color: "#a3a3a3", 129 + backgroundColor: "#1c1c1e", 130 + padding: "4px 12px", 131 + borderRadius: "6px", 132 + border: "1px solid #262626", 133 + }, 134 + children: name, 135 + }, 136 + })), 137 + ...(extra > 0 138 + ? [ 139 + { 140 + type: "div", 141 + props: { 142 + style: { 143 + fontSize: "14px", 144 + color: "#666", 145 + padding: "4px 8px", 146 + }, 147 + children: `+${extra} more`, 148 + }, 149 + }, 150 + ] 151 + : []), 152 + ], 153 + }, 154 + }, 155 + { 156 + type: "div", 157 + props: { 158 + style: { 159 + fontSize: "16px", 160 + color: "#666", 161 + flexShrink: 0, 162 + }, 163 + children: `${nodeCount} node${nodeCount !== 1 ? "s" : ""}`, 164 + }, 165 + }, 166 + ], 167 + }, 168 + }, 169 + ], 170 + }, 171 + }, 172 + { 173 + width: 1200, 174 + height: 630, 175 + fonts: [ 176 + { name: "Inter", data: interRegular, weight: 400, style: "normal" as const }, 177 + { name: "Inter", data: interBold, weight: 700, style: "normal" as const }, 178 + ], 179 + }, 180 + ); 181 + 182 + const resvg = new Resvg(svg, { 183 + fitTo: { mode: "width", value: 1200 }, 184 + }); 185 + const png = Buffer.from(resvg.render().asPng()); 186 + 187 + cache.set(id, png); 188 + return png; 189 + }
+10 -2
src/template.ts
··· 5 5 shareServerUrl?: string; 6 6 diagramId?: string; 7 7 existingShareUrl?: string; 8 + baseUrl?: string; 8 9 } 9 10 10 11 export function generateViewerHTML(diagram: WalkthroughDiagram, gitHash: string = "dev", projectRoot: string = "", options: ViewerOptions = {}): string { 11 12 const diagramJSON = JSON.stringify(diagram).replace(/<\//g, "<\\/"); 12 - const { mode = "local", shareServerUrl = "", diagramId = "", existingShareUrl = "" } = options; 13 + const { mode = "local", shareServerUrl = "", diagramId = "", existingShareUrl = "", baseUrl = "" } = options; 13 14 14 15 return `<!DOCTYPE html> 15 16 <html lang="en"> ··· 17 18 <meta charset="UTF-8" /> 18 19 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 19 20 <title>Traverse — ${escapeHTML(diagram.summary)}</title> 20 - <link rel="icon" href="/icon.svg" type="image/svg+xml" /> 21 + <link rel="icon" href="/icon.svg" type="image/svg+xml" />${baseUrl && diagramId ? ` 22 + <meta property="og:type" content="website" /> 23 + <meta property="og:title" content="${escapeHTML(diagram.summary)}" /> 24 + <meta property="og:description" content="Interactive code walkthrough with ${Object.keys(diagram.nodes).length} nodes" /> 25 + <meta property="og:image" content="${escapeHTML(baseUrl)}/diagram/${escapeHTML(diagramId)}/og.png" /> 26 + <meta name="twitter:card" content="summary_large_image" /> 27 + <meta name="twitter:title" content="${escapeHTML(diagram.summary)}" /> 28 + <meta name="twitter:image" content="${escapeHTML(baseUrl)}/diagram/${escapeHTML(diagramId)}/og.png" />` : ""} 21 29 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github-dark.min.css" id="hljs-dark" disabled /> 22 30 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github.min.css" id="hljs-light" /> 23 31 <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>