From 57533754c75d4bca14d81c630dcf73d4f2b35ade Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 21 Jul 2026 01:11:39 +0000 Subject: [PATCH] fix: restore readable README (was double-base64-encoded) Re-commit the org profile as plain markdown. The prior commit passed pre-encoded content, which the API base64-encoded a second time and rendered as gibberish. Co-Authored-By: Claude --- README.md | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9439be0..fd1ca77 100644 --- a/README.md +++ b/README.md @@ -1 +1,154 @@ -PGRpdiBhbGlnbj0iY2VudGVyIj4KCiMgUnVuaWMgR2F0ZXdheQoKKipBIHdlYnNpdGUgKyBnYW1lIGJyaWRnZSBmb3IgcHJpdmF0ZSBVbHRpbWEgT25saW5lIChTZXJ2VU8pIHNoYXJkcy4qKgoKUnVuaWMgR2F0ZXdheSBpcyBhIHNlbGYtaG9zdGFibGUgcGxhdGZvcm0gdGhhdCBnaXZlcyBhIFVPIHNoYXJkIGEgcHVibGljIHNpdGUsIHdpa2ksCmFuZCBhZG1pbiBwYW5lbCDigJQgYW5kIHdpcmVzIGl0IHRvIHRoZSAqbGl2ZSBpbi1nYW1lIHdvcmxkKiBzbyB0aGUgc2l0ZSBjYW4gc2hvdyBzaGFyZApzdGF0dXMsIGVjb25vbXksIElET0NzLCBwbGF5ZXIgYWN0aXZpdHksIGFuZCBwZXItY2hhcmFjdGVyIHNoZWV0cywgYW5kIHN0YWZmIGNhbiBwdXNoCmNvbnRyb2wgY29tbWFuZHMgYmFjayBpbnRvIHRoZSBnYW1lLiBQbGF5ZXJzIGdldCB0aGUgc2FtZSBwdWJsaWMgY29udGVudCBhbmQgc2VsZi1zZXJ2aWNlCm9uIHRoZSB3ZWIgKipvcioqIGEgbmF0aXZlIEFuZHJvaWQgYXBwLiBCcmFuZGluZyBpcyBpbnN0YW5jZS1jb25maWd1cmFibGUuCgpbIVt3ZWJzaXRlIMK3IGJ1aWxkIGltYWdlc10oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS93ZWJzaXRlL2FjdGlvbnMvd29ya2Zsb3dzL2J1aWxkLWltYWdlcy55bWwvYmFkZ2Uuc3ZnKV0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS93ZWJzaXRlL2FjdGlvbnM/d29ya2Zsb3c9YnVpbGQtaW1hZ2VzLnltbCkKWyFbbGluayDCtyByZWxlYXNlXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L2xpbmsvYWN0aW9ucy93b3JrZmxvd3MvcmVsZWFzZS55bWwvYmFkZ2Uuc3ZnKV0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9saW5rL2FjdGlvbnM/d29ya2Zsb3c9cmVsZWFzZS55bWwpClshW2FuZHJvaWQgwrcgY2hlY2tzXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L0FuZHJvaWQtYXBwL2FjdGlvbnMvd29ya2Zsb3dzL3ByLWNoZWNrcy55bWwvYmFkZ2Uuc3ZnKV0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9BbmRyb2lkLWFwcC9hY3Rpb25zP3dvcmtmbG93PXByLWNoZWNrcy55bWwpCgo8L2Rpdj4KCi0tLQoKIyMgVGhlIHBpZWNlcwoKUnVuaWMgR2F0ZXdheSBpcyBmaXZlIHJlcG9zaXRvcmllcyB0aGF0IGRlcGxveSB0b2dldGhlciBidXQgYnVpbGQgaW5kZXBlbmRlbnRseToKCnwgUmVwbyB8IExhbmd1YWdlIHwgV2hhdCBpdCBpcyB8CnwtLS0tLS18LS0tLS0tLS0tLXwtLS0tLS0tLS0tLS18CnwgWyoqd2Vic2l0ZSoqXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L3dlYnNpdGUpIHwgSmF2YVNjcmlwdCAoTm9kZSArIFJlYWN0KSB8IFRoZSBmdWxsLXN0YWNrIGFwcCDigJQgRXhwcmVzcyBSRVNUIEFQSSArIE1hcmlhREIgKyBhIFJlYWN0L1ZpdGUgU1BBIChwdWJsaWMgc2l0ZSwgd2lraSwgYWRtaW4gcGFuZWwpLiBUaGlzIGlzIHRoZSB0aGluZyBwbGF5ZXJzIGFuZCBzdGFmZiBhY3R1YWxseSB2aXNpdCwgYW5kIHRoZSBzaW5nbGUgYmFja2VuZCBldmVyeSBjbGllbnQgdGFsa3MgdG8uIHwKfCBbKipsaW5rKipdKGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvbGluaykgfCBSdXN0IHwgVGhlICoqdW8tbGluayoqIHNpZGVjYXIuIFJ1bnMgbmV4dCB0byB0aGUgc2hhcmQsIHRlcm1pbmF0ZXMgYSBsb29wYmFjayBsaW5rIGZyb20gdGhlIGdhbWUsIGFuZCBleHBvc2VzIHRoZSBhdXRoZW50aWNhdGVkIFdlYlNvY2tldCArIFJFU1QgQVBJIHRoZSB3ZWJzaXRlIGNvbnN1bWVzLiBUaGUgb25seSBuZXR3b3JrLWZhY2luZyBoYWxmIG9mIHRoZSBicmlkZ2UuIHwKfCBbKipzZXJ2dW8tcGx1Z2lucyoqXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L3NlcnZ1by1wbHVnaW5zKSB8IEMjIHwgVGhlICoqU2VydlVPIHBsdWdpbioqIOKAlCB0aGUgc2hhcmQgc2lkZSBvZiB0aGUgYnJpZGdlLiBDb21waWxlZCBieSBTZXJ2VU8gYXQgYm9vdDsgZGlhbHMgdGhlIHNpZGVjYXIgb3ZlciBsb29wYmFjayBhbmQgZW1pdHMgZ2FtZSBldmVudHMgLyBhY2NlcHRzIGNvbW1hbmRzLiB8CnwgWyoqQW5kcm9pZC1hcHAqKl0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9BbmRyb2lkLWFwcCkgfCBLb3RsaW4gKEpldHBhY2sgQ29tcG9zZSkgfCBUaGUgKipuYXRpdmUgQW5kcm9pZCBjbGllbnQqKiDigJQgcHVibGljIGNvbnRlbnQgKyBwbGF5ZXIgc2VsZi1zZXJ2aWNlLCBwdXJlbHkgYW4gQVBJIGNsaWVudCBvZiB0aGUgd2Vic2l0ZSBiYWNrZW5kLiBTYW1lIGZlYXR1cmVzIGFzIHRoZSBicm93c2VyIGNsaWVudCAqbWludXMqIGV2ZXJ5IGFkbWluIGNvbnNvbGUuIE5ldmVyIHRvdWNoZXMgdGhlIHNpZGVjYXIgb3Igc2hhcmQuIHwKfCBbKipkb2NzKipdKGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvZG9jcykgfCBNYXJrZG93biB8IEFsbCBwcm9qZWN0IGRvY3VtZW50YXRpb24g4oCUIGRlc2lnbiBkb2NzLCB0aGUgd2lyZS1wcm90b2NvbCBzcGVjLCB0aGUgaW50ZWdyYXRpb24gZ3VpZGUsIHRoZSBBbmRyb2lkIHBsYW4sIHJlc2VhcmNoLiBTdGFydCBoZXJlIHdoZW4geW91IHdhbnQgdGhlICp3aHkqLiB8CgojIyBIb3cgdGhleSBmaXQgdG9nZXRoZXIKCmBgYAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIOKUjOKUgOKWtiBicm93c2VyICAgICAgIHNhbWUtb3JpZ2luIEpTT04gLyBTU0UKU2VydlVPIHNoYXJkICDilIDilIBsb29wYmFjayBUQ1As4pSA4pSA4pa2ICB1by1saW5rIHNpZGVjYXIgIOKUgOKUgFdTICsgUkVTVOKUgOKUgOKWtiAgd2Vic2l0ZSBiYWNrZW5kIOKUgOKUpAooc2VydnVvLXBsdWdpbnMpIG5ld2xpbmUtSlNPTiAgICAgIChsaW5rLCBSdXN0KSAgICAgIGJlYXJlci1hdXRoICAgKHdlYnNpdGUsIE5vZGUpICDilJTilIDilrYgQW5kcm9pZCBhcHAgICBSRVNUICsgcHVzaCAobnRmeSkKYGBgCgotIFRoZSAqKnNoYXJkIGlzIG5ldmVyIGV4cG9zZWQgdG8gdGhlIGludGVybmV0LioqIEl0IG9ubHkgZGlhbHMgYDEyNy4wLjAuMWAuIFRoZSAqKnNpZGVjYXIqKgogIGlzIHRoZSBzb2xlIG5ldHdvcmstZmFjaW5nIGNvbXBvbmVudCwgYW5kIG9ubHkgdGhlIHdlYnNpdGUncyBiYWNrZW5kIHRhbGtzIHRvIGl0LgotIFRoZSAqKndlYnNpdGUgYmFja2VuZCoqIGluZ2VzdHMgYSBsaXZlIGV2ZW50IHN0cmVhbSBmcm9tIHRoZSBzaWRlY2FyIChsb2dpbnMsIHZpdGFscywKICBlY29ub215LCB2ZW5kb3Igc2FsZXMsIGRlYXRocywgSURPQyBkZWNheSwgc3RhZmYgYXVkaXTigKYpIGFuZCBtYWtlcyBwb2ludC1pbi10aW1lIFJFU1QKICBjYWxscyBmb3Igcm9zdGVycyBhbmQgY2hhcmFjdGVyIHNoZWV0cy4gSXQgdGhlbiBmYW5zIHRoYXQgb3V0IHRvIGJyb3dzZXJzIG92ZXIKICBTZXJ2ZXItU2VudCBFdmVudHMg4oCUIGEgcHVibGljIGNoYW5uZWwgKHNhZmUga2luZHMgb25seSkgYW5kIGFuIGFkbWluIGNoYW5uZWwgKGV2ZXJ5dGhpbmcpLgotIFRoZSAqKkFuZHJvaWQgYXBwKiogaXMgKmp1c3QgYW5vdGhlciBjbGllbnQgb2YgdGhlIHdlYnNpdGUgYmFja2VuZCog4oCUIGl0IHNwZWFrcyB0aGUgc2FtZQogIHB1YmxpYyBSRVNUIEFQSSBhbmQgbmV2ZXIgdGFsa3MgdG8gdGhlIHNpZGVjYXIgb3Igc2hhcmQuIEl0IHNlbGYtY29uZmlndXJlcyBpdHMgc2VydmVyIFVSTAogIG9uIGZpcnN0IHJ1biwgc28gb25lIGJ1aWxkIHdvcmtzIGFnYWluc3QgYW55IHNoYXJkLCBhbmQgcmVjZWl2ZXMgcHVzaCBub3RpZmljYXRpb25zIHRocm91Z2gKICB0aGUgc2hhcmQncyBzZWxmLWhvc3RlZCAqKm50ZnkqKiByZWxheSAobm8gR29vZ2xlIFBsYXkgU2VydmljZXMgcmVxdWlyZWQpLgotIFBsYXllcnMgKipsaW5rKiogYSBnYW1lIGFjY291bnQgdG8gYSB3ZWJzaXRlIGFjY291bnQgd2l0aCBhIG9uZS10aW1lIGluLWdhbWUgY29kZSwgd2hpY2gKICBpcyB3aGF0IGF1dGhvcml6ZXMgY2hhcmFjdGVyIHJlYWRzLiBTdGFmZiBjYW4gcHVzaCB0b3duLWNyaWVyIG1lc3NhZ2VzIGFuZCBjb250cm9sCiAgY29tbWFuZHMgYmFjayBpbnRvIHRoZSBnYW1lLgoKVGhlIGNvbm5lY3Rpb24gYmV0d2VlbiB3ZWJzaXRlIGFuZCBzaWRlY2FyIChVUkwsIHNoYXJlZC1zZWNyZXQgdG9rZW4sIHByb3RvY29sIHZlcnNpb24pIGlzCioqYWRtaW4tbWFuYWdlZCBpbiB0aGUgZGF0YWJhc2UqKiwgbm90IGVudiDigJQgc2V0IG9uY2UgaW4gdGhlIHNpdGUncyAqKkFkbWluIOKGkiBTaGFyZCoqIHBhbmVsLgpJZiB0aGUgc2lkZWNhciBpcyBhYnNlbnQgb3IgdGhlIHNoYXJkIGlzIGRvd24sIGV2ZXJ5IHNoYXJkIHN1cmZhY2UgZGVncmFkZXMgZ3JhY2VmdWxseS4KCi0tLQoKIyMgUXVpY2sgc3RhcnQKClRoZSBjb3JlIG9mIGEgbGl2ZSBzaGFyZCBpcyAqKnR3byoqIHRoaW5ncyBydW5uaW5nOiB0aGUgKip3ZWJzaXRlKiogKHNpdGUgKyBhZG1pbikgYW5kIHRoZQoqKmxpbmsqKiBzaWRlY2FyICh0aGUgZ2FtZSBicmlkZ2UpLiBUaGUgKipzZXJ2dW8tcGx1Z2lucyoqIGdldCBkZXBsb3llZCBpbnRvIHlvdXIgU2VydlVPIHNlcnZlcgpyb290IGFuZCBjb21waWxlIGF0IHNoYXJkIGJvb3Q7IHRoZSAqKkFuZHJvaWQtYXBwKiogaXMgYW4gb3B0aW9uYWwgY2xpZW50IHlvdSBwb2ludCBhdCB5b3VyCnJ1bm5pbmcgd2Vic2l0ZSDigJQgc2VlIGVhY2ggcmVwbydzIFJFQURNRS4KCiMjIyAxLiBXZWJzaXRlIOKAlCB0aGUgc2l0ZSArIGFkbWluIHBhbmVsCgpQcmVyZXFzOiAqKk5vZGUuanMgMjArKiogYW5kICoqRG9ja2VyKiogKGZvciBNYXJpYURCKS4KCmBgYGJhc2gKZ2l0IGNsb25lIGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvd2Vic2l0ZS5naXQKY2Qgd2Vic2l0ZQoKIyBTdGFydCBhIE1hcmlhREIgdGhlIGJhY2tlbmQgY2FuIHJlYWNoCmRvY2tlciBydW4gLWQgLS1uYW1lIHJnLWRiIC1wIDMzMDY6MzMwNiBcCiAgLWUgTUFSSUFEQl9EQVRBQkFTRT1ydW5pY19nYXRld2F5IC1lIE1BUklBREJfVVNFUj1ydW5pYyBcCiAgLWUgTUFSSUFEQl9QQVNTV09SRD1kZXZwYXNzIC1lIE1BUklBREJfUk9PVF9QQVNTV09SRD1yb290cGFzcyBtYXJpYWRiOjExCgojIENvbmZpZ3VyZSArIHN0YXJ0IHRoZSBiYWNrZW5kICh0ZXJtaW5hbCAxKQpjcCBzZXJ2ZXIvLmVudi5leGFtcGxlIHNlcnZlci8uZW52CiMgICBzZXQgREJfSE9TVD0xMjcuMC4wLjEsIERCX1BPUlQ9MzMwNiwgREJfVVNFUj1ydW5pYywgREJfUEFTU1dPUkQ9ZGV2cGFzcywKIyAgICAgICBKV1RfU0VDUkVUPTxhbnl0aGluZz4sIEFETUlOX1VTRVJOQU1FPWFkbWluLCBBRE1JTl9QQVNTV09SRD08eW91ciBwYXNzd29yZD4KbnBtIHJ1biBpbnN0YWxsLXNlcnZlcgpucG0gcnVuIHNlcnZlciAgICAgICAgICAgICMgbm9kZW1vbiDihpIgaHR0cDovL2xvY2FsaG9zdDozMDAwCgojIFN0YXJ0IHRoZSBmcm9udGVuZCAodGVybWluYWwgMikKbnBtIHJ1biBpbnN0YWxsLWNsaWVudApucG0gcnVuIGNsaWVudCAgICAgICAgICAgICMgVml0ZSDihpIgaHR0cDovL2xvY2FsaG9zdDo1MTczCmBgYAoKT3BlbiAqKmh0dHA6Ly9sb2NhbGhvc3Q6NTE3MyoqLCBzaWduIGluIGF0ICoqYC9hZG1pbi9sb2dpbmAqKiB3aXRoIHRoZSBhZG1pbiBjcmVkZW50aWFscwp5b3Ugc2V0LCB0aGVuIGZsaXAgKipNYWludGVuYW5jZSDihpIgTGl2ZSoqIG9uIHRoZSBEYXNoYm9hcmQuIFRhYmxlcywgZGVmYXVsdHMsIGFuZCB0aGUgZmlyc3QKYWRtaW4gYXJlIGNyZWF0ZWQgYXV0b21hdGljYWxseSBvbiBmaXJzdCBib290LgoKPiAqKlByb2R1Y3Rpb24gKERvY2tlciBDb21wb3NlKToqKiB0aGUgd2Vic2l0ZSBzaGlwcyBhIHByb2R1Y3Rpb24tc2hhcGVkCj4gYGRvY2tlci1jb21wb3NlLnltbGAgdGhhdCAqcHVsbHMqIHByZWJ1aWx0IGBhcHBgICsgYGJvdGAgaW1hZ2VzIGFuZCBzZXJ2ZXMgdGhlIGJ1aWx0IFNQQQo+IGZyb20gRXhwcmVzcyDigJQgYGNwIC5lbnYuZXhhbXBsZSAuZW52YCwgZmlsbCBpdCBpbiwgdGhlbgo+IGBkb2NrZXIgY29tcG9zZSBwdWxsICYmIGRvY2tlciBjb21wb3NlIHVwIC1kYC4gU2VlIHRoZSB3ZWJzaXRlIFJFQURNRSBmb3IgdGhlIGZ1bGwgb3B0aW9ucy4KCiMjIyAyLiBsaW5rIOKAlCB0aGUgdW8tbGluayBzaWRlY2FyCgpQcmVyZXFzOiAqKlJ1c3QqKiAoY2FyZ28pLiBTdGFuZGFyZCBjYXJnbyBjcmF0ZToKCmBgYGJhc2gKZ2l0IGNsb25lIGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvbGluay5naXQKY2QgbGluay9zaWRlY2FyCmNhcmdvIGJ1aWxkIC0tcmVsZWFzZSAgICAgICAgICAjIGJpbmFyeSBhdCB0YXJnZXQvcmVsZWFzZS91by1saW5rLXNpZGVjYXIKY3Agc2lkZWNhci50b21sLmV4YW1wbGUgc2lkZWNhci50b21sICAgIyB0aGVuIGVkaXQgKGJpbmQgYWRkcnMsIHNoYXJlZC1zZWNyZXQgdG9rZW4pCmNhcmdvIHJ1biAtLXJlbGVhc2UKYGBgCgpUaGVuIHBvaW50IHRoZSB3ZWJzaXRlIGF0IGl0IGZyb20gKipBZG1pbiDihpIgU2hhcmQqKjogc2V0IHRoZSBzaWRlY2FyJ3MgYmFzZSBVUkwsIFdlYlNvY2tldApVUkwsIHNoYXJlZC1zZWNyZXQgdG9rZW4sIGFuZCBwcm90b2NvbCB2ZXJzaW9uLiBQcmVidWlsdCBMaW51eCArIFdpbmRvd3MgYmluYXJpZXMgYXJlIGFsc28KY3V0IGFzIGEgR2l0ZWEgcmVsZWFzZSBvbiBldmVyeSBtZXJnZSB0byBgbWFpbmAuCgojIyMgMy4gc2VydnVvLXBsdWdpbnMg4oCUIHRoZSBzaGFyZCBzaWRlCgpEZXBsb3llZCBhcyAqKnNvdXJjZSoqIGludG8geW91ciBTZXJ2VU8gc2VydmVyIHJvb3QgYW5kIGNvbXBpbGVkIGJ5IFNlcnZVTyBhdCBib290IChubyBidWlsZAphcnRpZmFjdCwgbm8gQ0kpLiBJdCBkaWFscyB0aGUgc2lkZWNhciBvdmVyIGxvb3BiYWNrLiBTZWUKW3NlcnZ1by1wbHVnaW5zXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L3NlcnZ1by1wbHVnaW5zKSBmb3IgZGVwbG95CnN0ZXBzIGFuZCB0aGUgY29tcGF0aWJpbGl0eSBub3Rlcy4KCiMjIyA0LiBBbmRyb2lkLWFwcCDigJQgdGhlIG5hdGl2ZSBjbGllbnQgKG9wdGlvbmFsKQoKUHJlcmVxczogKipKREsgMTcqKiBhbmQgdGhlIEFuZHJvaWQgU0RLLiBBIHNpbmdsZSBidWlsZCB3b3JrcyBhZ2FpbnN0IGFueSBzaGFyZCDigJQgdGhlIGFwcApwcm9tcHRzIGZvciB5b3VyIHdlYnNpdGUncyBVUkwgb24gZmlyc3QgcnVuLgoKYGBgYmFzaApnaXQgY2xvbmUgaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9BbmRyb2lkLWFwcC5naXQKY2QgQW5kcm9pZC1hcHAKLi9ncmFkbGV3IGFzc2VtYmxlRGVidWcgICAgICAgICMgZGVidWcgQVBLIOKGkiBhcHAvYnVpbGQvb3V0cHV0cy9hcGsvZGVidWcvCi4vZ3JhZGxldyBpbnN0YWxsRGVidWcgICAgICAgICAjIGluc3RhbGwgb24gYSBjb25uZWN0ZWQgZGV2aWNlIC8gZW11bGF0b3IKYGBgCgpLb3RsaW4gKyBKZXRwYWNrIENvbXBvc2UgKE1hdGVyaWFsIDMpLCBtaW4gU0RLIEFuZHJvaWQgMTAgKEFQSSAyOSkuIEl0IHN1cmZhY2VzIHRoZSBzYW1lCnB1YmxpYyBjb250ZW50IGFuZCBwbGF5ZXIgc2VsZi1zZXJ2aWNlIGFzIHRoZSBicm93c2VyIOKAlCBob21lL3N0YXR1cywgbmV3cywgd2lraSwgdGhlIHNoYXJkCmh1YiwgYWNjb3VudCBsaW5raW5nLCBjaGFyYWN0ZXIgc2hlZXRzIOKAlCBhbmQgb3B0cyBpbnRvIHB1c2ggbm90aWZpY2F0aW9ucyB0aHJvdWdoIHRoZSBzaGFyZCdzCnNlbGYtaG9zdGVkICoqbnRmeSoqIHJlbGF5LiBTZWUgdGhlIFtBbmRyb2lkLWFwcCBSRUFETUVdKGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvQW5kcm9pZC1hcHApCmFuZCB0aGUgZGVzaWduIGNvbnRyYWN0IGluIFtgZG9jcy9hbmRyb2lkL1BMQU4ubWRgXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L2RvY3MpLgoKLS0tCgojIyBXaGVyZSB0byBnbyBuZXh0CgotICoqUnVubmluZyAvIGN1c3RvbWl6aW5nIHRoZSBzaXRlKiog4oaSIFt3ZWJzaXRlIFJFQURNRV0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS93ZWJzaXRlKSAodGVjaCBzdGFjaywgQVBJIGVuZHBvaW50cywgZW52IHZhcnMsIHNlY3VyaXR5LCBicmFuZGluZywgU3dhZ2dlciBhdCBgL2FwaS9kb2NzYCkuCi0gKipUaGUgQW5kcm9pZCBjbGllbnQqKiDihpIgW0FuZHJvaWQtYXBwIFJFQURNRV0oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9BbmRyb2lkLWFwcCkgYW5kIFtgZG9jcy9hbmRyb2lkL1BMQU4ubWRgXShodHRwczovL2dpdGVhLndoaXRsb2NrdGVjaC5jb20vUnVuaWNHYXRld2F5L2RvY3MpIOKAlCB0aGUgYXV0aG9yaXRhdGl2ZSBkZXNpZ24gY29udHJhY3QsIG1pbGVzdG9uZXMsIGFuZCB0aGUgcHVzaC1ub3RpZmljYXRpb24gYXJjaGl0ZWN0dXJlLgotICoqVGhlIGJyaWRnZSBpbnRlcm5hbHMqKiDihpIgW2RvY3NdKGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvZG9jcykg4oCUIGRlc2lnbiBkb2NzLCB0aGUgY2Fub25pY2FsIHdpcmUtcHJvdG9jb2wgc3BlYywgYW5kIHRoZSBpbnRlZ3JhdGlvbiBndWlkZS4KLSAqKkRlcGxveWluZyB0aGUgc2lkZWNhciAvIHBsdWdpbiB0b2dldGhlcioqIOKGkiBbbGluayBSRUFETUVdKGh0dHBzOi8vZ2l0ZWEud2hpdGxvY2t0ZWNoLmNvbS9SdW5pY0dhdGV3YXkvbGluaykgYW5kIFtzZXJ2dW8tcGx1Z2luc10oaHR0cHM6Ly9naXRlYS53aGl0bG9ja3RlY2guY29tL1J1bmljR2F0ZXdheS9zZXJ2dW8tcGx1Z2lucykuCgo8ZGl2IGFsaWduPSJjZW50ZXIiPgo8c3ViPlJ1bmljIEdhdGV3YXkgwrcgd2hpdGxvY2t0ZWNoQGdtYWlsLmNvbTwvc3ViPgo8L2Rpdj4K \ No newline at end of file +
+ +# Runic Gateway + +**A website + game bridge for private Ultima Online (ServUO) shards.** + +Runic Gateway is a self-hostable platform that gives a UO shard a public site, wiki, +and admin panel — and wires it to the *live in-game world* so the site can show shard +status, economy, IDOCs, player activity, and per-character sheets, and staff can push +control commands back into the game. Players get the same public content and self-service +on the web **or** a native Android app. Branding is instance-configurable. + +[![website · build images](https://gitea.whitlocktech.com/RunicGateway/website/actions/workflows/build-images.yml/badge.svg)](https://gitea.whitlocktech.com/RunicGateway/website/actions?workflow=build-images.yml) +[![link · release](https://gitea.whitlocktech.com/RunicGateway/link/actions/workflows/release.yml/badge.svg)](https://gitea.whitlocktech.com/RunicGateway/link/actions?workflow=release.yml) +[![android · checks](https://gitea.whitlocktech.com/RunicGateway/Android-app/actions/workflows/pr-checks.yml/badge.svg)](https://gitea.whitlocktech.com/RunicGateway/Android-app/actions?workflow=pr-checks.yml) + +
+ +--- + +## The pieces + +Runic Gateway is five repositories that deploy together but build independently: + +| Repo | Language | What it is | +|------|----------|------------| +| [**website**](https://gitea.whitlocktech.com/RunicGateway/website) | JavaScript (Node + React) | The full-stack app — Express REST API + MariaDB + a React/Vite SPA (public site, wiki, admin panel). This is the thing players and staff actually visit, and the single backend every client talks to. | +| [**link**](https://gitea.whitlocktech.com/RunicGateway/link) | Rust | The **uo-link** sidecar. Runs next to the shard, terminates a loopback link from the game, and exposes the authenticated WebSocket + REST API the website consumes. The only network-facing half of the bridge. | +| [**servuo-plugins**](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins) | C# | The **ServUO plugin** — the shard side of the bridge. Compiled by ServUO at boot; dials the sidecar over loopback and emits game events / accepts commands. | +| [**Android-app**](https://gitea.whitlocktech.com/RunicGateway/Android-app) | Kotlin (Jetpack Compose) | The **native Android client** — public content + player self-service, purely an API client of the website backend. Same features as the browser client *minus* every admin console. Never touches the sidecar or shard. | +| [**docs**](https://gitea.whitlocktech.com/RunicGateway/docs) | Markdown | All project documentation — design docs, the wire-protocol spec, the integration guide, the Android plan, research. Start here when you want the *why*. | + +## How they fit together + +``` + ┌─▶ browser same-origin JSON / SSE +ServUO shard ──loopback TCP,──▶ uo-link sidecar ──WS + REST──▶ website backend ─┤ +(servuo-plugins) newline-JSON (link, Rust) bearer-auth (website, Node) └─▶ Android app REST + push (ntfy) +``` + +- The **shard is never exposed to the internet.** It only dials `127.0.0.1`. The **sidecar** + is the sole network-facing component, and only the website's backend talks to it. +- The **website backend** ingests a live event stream from the sidecar (logins, vitals, + economy, vendor sales, deaths, IDOC decay, staff audit…) and makes point-in-time REST + calls for rosters and character sheets. It then fans that out to browsers over + Server-Sent Events — a public channel (safe kinds only) and an admin channel (everything). +- The **Android app** is *just another client of the website backend* — it speaks the same + public REST API and never talks to the sidecar or shard. It self-configures its server URL + on first run, so one build works against any shard, and receives push notifications through + the shard's self-hosted **ntfy** relay (no Google Play Services required). +- Players **link** a game account to a website account with a one-time in-game code, which + is what authorizes character reads. Staff can push town-crier messages and control + commands back into the game. + +The connection between website and sidecar (URL, shared-secret token, protocol version) is +**admin-managed in the database**, not env — set once in the site's **Admin → Shard** panel. +If the sidecar is absent or the shard is down, every shard surface degrades gracefully. + +--- + +## Quick start + +The core of a live shard is **two** things running: the **website** (site + admin) and the +**link** sidecar (the game bridge). The **servuo-plugins** get deployed into your ServUO server +root and compile at shard boot; the **Android-app** is an optional client you point at your +running website — see each repo's README. + +### 1. Website — the site + admin panel + +Prereqs: **Node.js 20+** and **Docker** (for MariaDB). + +```bash +git clone https://gitea.whitlocktech.com/RunicGateway/website.git +cd website + +# Start a MariaDB the backend can reach +docker run -d --name rg-db -p 3306:3306 \ + -e MARIADB_DATABASE=runic_gateway -e MARIADB_USER=runic \ + -e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11 + +# Configure + start the backend (terminal 1) +cp server/.env.example server/.env +# set DB_HOST=127.0.0.1, DB_PORT=3306, DB_USER=runic, DB_PASSWORD=devpass, +# JWT_SECRET=, ADMIN_USERNAME=admin, ADMIN_PASSWORD= +npm run install-server +npm run server # nodemon → http://localhost:3000 + +# Start the frontend (terminal 2) +npm run install-client +npm run client # Vite → http://localhost:5173 +``` + +Open **http://localhost:5173**, sign in at **`/admin/login`** with the admin credentials +you set, then flip **Maintenance → Live** on the Dashboard. Tables, defaults, and the first +admin are created automatically on first boot. + +> **Production (Docker Compose):** the website ships a production-shaped +> `docker-compose.yml` that *pulls* prebuilt `app` + `bot` images and serves the built SPA +> from Express — `cp .env.example .env`, fill it in, then +> `docker compose pull && docker compose up -d`. See the website README for the full options. + +### 2. link — the uo-link sidecar + +Prereqs: **Rust** (cargo). Standard cargo crate: + +```bash +git clone https://gitea.whitlocktech.com/RunicGateway/link.git +cd link/sidecar +cargo build --release # binary at target/release/uo-link-sidecar +cp sidecar.toml.example sidecar.toml # then edit (bind addrs, shared-secret token) +cargo run --release +``` + +Then point the website at it from **Admin → Shard**: set the sidecar's base URL, WebSocket +URL, shared-secret token, and protocol version. Prebuilt Linux + Windows binaries are also +cut as a Gitea release on every merge to `main`. + +### 3. servuo-plugins — the shard side + +Deployed as **source** into your ServUO server root and compiled by ServUO at boot (no build +artifact, no CI). It dials the sidecar over loopback. See +[servuo-plugins](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins) for deploy +steps and the compatibility notes. + +### 4. Android-app — the native client (optional) + +Prereqs: **JDK 17** and the Android SDK. A single build works against any shard — the app +prompts for your website's URL on first run. + +```bash +git clone https://gitea.whitlocktech.com/RunicGateway/Android-app.git +cd Android-app +./gradlew assembleDebug # debug APK → app/build/outputs/apk/debug/ +./gradlew installDebug # install on a connected device / emulator +``` + +Kotlin + Jetpack Compose (Material 3), min SDK Android 10 (API 29). It surfaces the same +public content and player self-service as the browser — home/status, news, wiki, the shard +hub, account linking, character sheets — and opts into push notifications through the shard's +self-hosted **ntfy** relay. See the [Android-app README](https://gitea.whitlocktech.com/RunicGateway/Android-app) +and the design contract in [`docs/android/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs). + +--- + +## Where to go next + +- **Running / customizing the site** → [website README](https://gitea.whitlocktech.com/RunicGateway/website) (tech stack, API endpoints, env vars, security, branding, Swagger at `/api/docs`). +- **The Android client** → [Android-app README](https://gitea.whitlocktech.com/RunicGateway/Android-app) and [`docs/android/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs) — the authoritative design contract, milestones, and the push-notification architecture. +- **The bridge internals** → [docs](https://gitea.whitlocktech.com/RunicGateway/docs) — design docs, the canonical wire-protocol spec, and the integration guide. +- **Deploying the sidecar / plugin together** → [link README](https://gitea.whitlocktech.com/RunicGateway/link) and [servuo-plugins](https://gitea.whitlocktech.com/RunicGateway/servuo-plugins). + +
+Runic Gateway · whitlocktech@gmail.com +