{"id":33349700,"url":"https://github.com/rfinz/xu60","last_synced_at":"2026-04-05T11:31:45.098Z","repository":{"id":318244505,"uuid":"1070005498","full_name":"rfinz/xu60","owner":"rfinz","description":"needed now more than ever","archived":false,"fork":false,"pushed_at":"2026-01-21T04:24:17.000Z","size":398,"stargazers_count":1,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-18T19:38:18.995Z","etag":null,"topics":["hypertext","syndication","versioning"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rfinz.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":null,"dco":null,"cla":null}},"created_at":"2025-10-05T04:11:56.000Z","updated_at":"2026-01-21T04:24:20.000Z","dependencies_parsed_at":"2025-10-06T03:24:18.665Z","dependency_job_id":"e9ac1169-498f-4e9b-96ae-b607cabe95ed","html_url":"https://github.com/rfinz/xu60","commit_stats":null,"previous_names":["rfinz/hyperhyper","rfinz/xu60"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/rfinz/xu60","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfinz%2Fxu60","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfinz%2Fxu60/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfinz%2Fxu60/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfinz%2Fxu60/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rfinz","download_url":"https://codeload.github.com/rfinz/xu60/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rfinz%2Fxu60/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31434624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T08:13:15.228Z","status":"ssl_error","status_checked_at":"2026-04-05T08:13:11.839Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["hypertext","syndication","versioning"],"created_at":"2025-11-22T09:00:36.917Z","updated_at":"2026-04-05T11:31:45.087Z","avatar_url":"https://github.com/rfinz.png","language":"Python","readme":"# xu60\nneeded now more than ever\n\n**xu60** is a \n- Content-Addressable \n- Hyper Text Transport Protocol \n- Application Programmatic Interface\n\nwhere hashes and character indexes\n\ndefine immutable URLs\n\nfor re-usable content, (mostly) guaranteed not to rot as a website grows, changes, is edited, or is otherwised enhanced (or regressed)\n\n## scope\n1. **xu60** should provide an HTTP server that is simultaneously capable of serving traditional web pages, scripts, and styles alongside whatever content-addressable data an application developer sees fit\n2. **xu60** should contain additional functionality for querying document version histories and displaying document metadata such as original paths, edit dates, etc.\n3. besides documents, content-addressable document histories, and document/history metadata, **xu60** should remain agnostic to use-case and ship as few features as is plausible\n4. **xu60** should virtually never re-code the core algorithms that make its features possible. high-performance versions of virtually every element already exist--**xu60** is a thin application layer built around known technologies. plus I'm dumb\n5. the \"known technologies\" that **xu60** relies on should (wherever possible) already be infrastructurally important to the web and unlikely to disappear or be deprecated with any rapidity\n6. **xu60** should be easy to run in a number of configurations to serve a number of applications--hypermedia-ing, mirroring, addressing, serving, distributing, archiving, auditing, etc.\n\n\n## installation and demo\n\n**xu60** comes with a demo application (built with htmx!! [under construction]) that serves as both a technical demonstration of the server's capabilities, proof-of-concept object browser, and tour of the server's own code.\n\n### source\n```sh\n# to run xu60 from its source:\ngit clone https://github.com/rfinz/xu60.git #clone the repository\ncd xu60 #navigate into the project\npython -m venv env #create a virtual environment\nsource env/bin/activate #activate the virtual environment\npip install -e . #install required dependencies and the xu60 application\nuvicorn xu60:app #run the asgi application -- you may need to install uvicorn separately\n# -\u003e the demo should now be available at 127.0.0.1:8000\n```\n\n### pypi\n```sh\n# to run as a package:\npip install xu60[server]\ncd \u003cyour git repo\u003e\nuvicorn xu60:app \n```\n\n# the interface\n**xu60** exposes three read-only endpoints for interacting with content-addressed documents.\n\nthe **xu60** interface is designed to be agnostic to back-end (not that they are [currently] swappable, but that there is nothing in principle stopping **xu60** from being based on another technology), but the current content-addressable object database is provided by [git](https://git-scm.com).\n\nall \"movement\" is accomplishable via URL segments. this means that if you want to change the piece of *content* that you are looking at, you should be able to get there in the `path/path/path/path` part of the URL. this leaves the query parameter part of the URL (under development) to freely alter the presentation of said content, plus configure units, encodings, etc.\n\n---\n\n## object\nthis is the primary affordance of **xu60**: the object API delivers document data based on its content id. it also supports server-side slicing of the document to deliver only requested character ('utf-8') ranges.\n\n### index\n#### `/object` \n→ a plaintext listing of all the available objects, an epoch (seconds) timestamp associated with each object's creation, object's reference name (for keeping track of versions), and length.\n\n\u003e ```\n\u003e GET /object\n\u003e\n\u003e object,time,name,length,indices\n\u003e 23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5,1764053925,xu60/main.py,10602,chars\n\u003e 7d31564ba2a01c8d75d01ed050a1185280da454c,1764044450,whitepapers/design.md,11802,chars\n\u003e 2acac76e44d2ace1cf3f8b395f4fbeeec26c6d50,1764042716,xu60/main.py,9720,chars\n\u003e e01a8f3de37189c322812df39dccebae82dac5c9,1763962600,README.md,2635,chars\n\u003e .\n\u003e .\n\u003e . (etc)\n\u003e ```\n\n### contents\n#### `/object/{object: str}` \n→ return the full contents of the document represented by the content id `object`\n\n\u003e ```\n\u003e GET /object/23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5   # a version of xu60/main.py\n\u003e\n\u003e \"\"\"\n\u003e Hopefully a single file server.\n\u003e \"\"\"\n\u003e import re\n\u003e import datetime\n\u003e .\n\u003e .\n\u003e . (etc)\n\u003e ```\n\n### slicing\nslicing is accomplished by adding a few url segments to the end of your object endpoint. the `-` character allows open ending slicing....you may leave off either the start or end index.\n\n#### `/object/{object: str}/{start: int}/-/{end: int}` \n→ contents between `start` and `end`\n\n#### `/object/{object: str}/{start: int}/-`\n→ contents between `start` and the end of the document\n\n#### `/object/{object: str}/-/{end: int}`\n→ contents between the beginning of the document and `end`\n\n\u003e ```\n\u003e GET /object/23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5/4/-/6\n\u003e \n\u003e Ho\n\u003e ```\n\n---\n\n## versions\nthe `versions` endpoint is the main way to query **xu60** about the presence of other document versions.\n\n### index\n#### `/versions`\n→ a plaintext listing of all the available objects, an epoch (seconds) timestamp associated with each object's creation, object's reference name (for keeping track of versions), and length. this is currently *identical* to the listing created by the `object` endpoint.\n\n\u003e ```\n\u003e GET /versions\n\u003e\n\u003e object,time,name,length,indices\n\u003e 23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5,1764053925,xu60/main.py,10602,chars\n\u003e 7d31564ba2a01c8d75d01ed050a1185280da454c,1764044450,whitepapers/design.md,11802,chars\n\u003e 2acac76e44d2ace1cf3f8b395f4fbeeec26c6d50,1764042716,xu60/main.py,9720,chars\n\u003e e01a8f3de37189c322812df39dccebae82dac5c9,1763962600,README.md,2635,chars\n\u003e .\n\u003e .\n\u003e . (etc)\n\u003e ```\n\n### versions for name\n#### `/versions/{name: path}`\n→ return all versions for a given name (`name` is a path-like string). versions are listed newest to oldest.\n\n\u003e ```\n\u003e GET /versions/xu60/main.py\n\u003e \n\u003e a9083f8cce97c1c83473000460f9de3bde35ac82\n\u003e 9c6b6c847e6682ec319ad64a0274c6f7ee366472\n\u003e c7563b7d2149cb8371647881a2f960b2f6dd544f\n\u003e 7d217e22d67e5e30b65ab8554a6866e5be3f004f\n\u003e cb80696bc75b9ebc69e7047eca5c6ea4cb15fc53\n\u003e 4b424a220ac9410307d0b82635d53bb1759acf2d\n\u003e d75af2786adcf34a71d875eba132c6f92048b0b6\n\u003e .\n\u003e .\n\u003e . (etc)\n\u003e ```\n\n### time slicing\nparallel to the way that the `object` interface allows slicing content by character indexes inside the document, the `versions` interface allows slicing by *second indexes inside of time*.\n\n#### `/versions/{name: path}/{start: int}/-/{end: int}`\n→ versions between `start` and `end` (epoch seconds)\n\n#### `/versions/{name: path}/{start: int}/-`\n→ versions between `start` and the end of time\n\n#### `/versions/{name: path}/-/{end: int}`\n→ versions between the beginning of time and `end`\n\n\nlet's say we want to grab the versions of `xu60/main.py` mentioned in the truncated output of `/versions` above, plus anything newer, just in case there's a new version since this README was written.\n\n\u003e ```\n\u003e GET /versions/xu60/main.py/1766035196/-   # notice no end time\n\u003e \n\u003e a9083f8cce97c1c83473000460f9de3bde35ac82\n\u003e 9c6b6c847e6682ec319ad64a0274c6f7ee366472\n\u003e c7563b7d2149cb8371647881a2f960b2f6dd544f\n\u003e 7d217e22d67e5e30b65ab8554a6866e5be3f004f\n\u003e ```\n\n---\n\n## meta\nthe `meta` endpoint delivers a more complete set of machine-readable metadata in json format. besides the main meta entrypoint, `/meta` wraps and modifies object and version endpoints. this endpoint is changing rapidly so I am intentionally leaving the documentation more sparse.\n\n\n### index\n#### `/meta` \n→ json containing site-level metadata.\n\n\u003e ```json\n\u003e GET /meta\n\u003e \n\u003e {\n\u003e   \"site\": \"http://127.0.0.1:8000\",\n\u003e   \"truth\": \"https://github.com/rfinz/xu60.git\",\n\u003e   \"head\": \"98f6ae10b7c6ad5c7f1e90a76626b4bb0fb0185e\",\n\u003e   \"last_updated\": \"2025-12-21 22:53:27\",\n\u003e   \"content_id\": \"sha1\",\n\u003e   \"mirrors\": {},\n\u003e   \"meta\": \"/meta\",\n\u003e   \"object\": \"/object\",\n\u003e   \"versions\": \"/versions\"\n\u003e }\n\u003e ```\n\n### wraps\n#### `/meta/{object endpoint}`\n→ more metadata about objects\n\n\u003e ```json\n\u003e GET /meta/object/23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5/4/-/6\n\u003e \n\u003e {\n\u003e   \"id\": \"23413cdfecdeb434cd5ae7ce8ea72e71fec1b0b5\",\n\u003e   \"names\": [\n\u003e     {\n\u003e       \"name\": \"xu60/main.py\",\n\u003e       \"commit_id\": \"300133b9b17d5bfbed20bb1b589afab61908423f\",\n\u003e       \"time\": 1764053925,\n\u003e       \"message\": \"probably needs more testing but introducing time slicing!\\n\"\n\u003e     }\n\u003e   ],\n\u003e   \"length\": 10603,\n\u003e   \"indices\": \"chars\",\n\u003e   \"window\": {\n\u003e     \"start\": 4,\n\u003e     \"end\": 6\n\u003e   },\n\u003e   \"previous_version\": {\n\u003e     \"id\": \"2acac76e44d2ace1cf3f8b395f4fbeeec26c6d50\",\n\u003e     \"changes\": [\n\u003e       {\n\u003e         \"from\": \"8216/-/8217\",\n\u003e         \"to\": \"8216/-/8243\"\n\u003e       },\n\u003e       {\n\u003e         \"from\": \"8281/-/8281\",\n\u003e         \"to\": \"8281/-/8764\"\n\u003e       },\n\u003e       {\n\u003e         \"from\": \"8268/-/8572\",\n\u003e         \"to\": \"8777/-/8812\"\n\u003e       },\n\u003e       {\n\u003e         \"from\": \"8907/-/8907\",\n\u003e         \"to\": \"8907/-/9549\"\n\u003e       }\n\u003e     ]\n\u003e   },\n\u003e   \"next_version\": {\n\u003e     \"id\": \"ee1b33e686015ba51b168111d12744abfa7ce1fd\",\n\u003e     \"changes\": [\n\u003e       {\n\u003e         \"from\": \"2142/-/2189\",\n\u003e         \"to\": \"2142/-/2185\"\n\u003e       },\n\u003e       {\n\u003e         \"from\": \"6630/-/6689\",\n\u003e         \"to\": \"6626/-/6681\"\n\u003e       },\n\u003e       {\n\u003e         \"from\": \"7723/-/7763\",\n\u003e         \"to\": \"7715/-/7751\"\n\u003e       }\n\u003e     ]\n\u003e   },\n\u003e   \"body\": \"Ho\"\n\u003e }\n\u003e ```\n\n#### `/meta/{versions endpoint}`\n→ more metadata about names and versions\n\n\u003e ```json\n\u003e GET /meta/versions/xu60/main.py/1766035196/-\n\u003e \n\u003e {\n\u003e   \"name\": \"xu60/main.py\",\n\u003e   \"versions\": [\n\u003e     {\n\u003e       \"id\": \"a9083f8cce97c1c83473000460f9de3bde35ac82\",\n\u003e       \"commit_id\": \"48508790c473cfc8cdbd25a2f42d6a2d9e9ea6f5\",\n\u003e       \"time\": 1766294358,\n\u003e       \"message\": \"being a janitor used to mean something\\n\"\n\u003e     },\n\u003e     {\n\u003e       \"id\": \"9c6b6c847e6682ec319ad64a0274c6f7ee366472\",\n\u003e       \"commit_id\": \"3b3d4458bcad078134bc730ca1c622d7a33e90ed\",\n\u003e       \"time\": 1766088642,\n\u003e       \"message\": \"fix bug, use cache for object requests\\n\"\n\u003e     },\n\u003e     {\n\u003e       \"id\": \"c7563b7d2149cb8371647881a2f960b2f6dd544f\",\n\u003e       \"commit_id\": \"f36337f94bc43eb69a986a9f77a82d82dae4a737\",\n\u003e       \"time\": 1766083424,\n\u003e       \"message\": \"implemented the nobody query finally\\n\"\n\u003e     },\n\u003e     {\n\u003e       \"id\": \"7d217e22d67e5e30b65ab8554a6866e5be3f004f\",\n\u003e       \"commit_id\": \"7abef08ae162f53d2f2b042d41e0f8e56b078240\",\n\u003e       \"time\": 1766035196,\n\u003e       \"message\": \"use bytes if binary data\\n\"\n\u003e     }\n\u003e   ]\n\u003e }\n\u003e ```\n\n## suggested types of **xu60** deployments\n- **\"[quinish](https://en.wikipedia.org/wiki/Quine_(computing))\"**\n\n  a site's git repository and the site itself are one in the same -- **xu60** delivers the contents on the www. The website for this project is itself a quinish [under construction].\n  \n- **version tracking**\n\n  a site maintains its display layer separate from its content (and outside of **xu60's** purview). The role of **xu60** is to maintain a history of the content, independent of its display details. Alternatively, **xu60** could be used to track changes to a site's display layer, even as the content is so dynamic that \"version control\" no longer makes sense.\n  \n- **diff+link**\n\n  use the **xu60** client to visualize changes of the content across time, and transclude and annotate content from external sites. The website for this project [under construction] is itself a quinish sort of diff+link\n  \n- **indieweb**\n\n  built on top any other type (version tracking, quinish, diff+link) of **xu60** deployment, linking to an object slice on a friend's deployment is adequate indication of a \"mention\". No need for a \"blog roll\", scraping your transclusions and links into a central table accomplishes most of that for you.\n\n- **content mirror**\n\n  use **xu60**'s mirroring mechanism to help keep important content on the web. Sites that are listed as full mirrors are automatically cached alongside the primary site contents, and have their objects browseable in an identical manner.\n  \n  \n\n---\n\n\u003e [!IMPORTANT]\n\u003e **xu60** is experimental and unstable :)\n\n---\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfinz%2Fxu60","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frfinz%2Fxu60","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frfinz%2Fxu60/lists"}