{"id":13569331,"url":"https://github.com/toddsundsted/ktistec","last_synced_at":"2026-03-03T19:04:38.104Z","repository":{"id":42038207,"uuid":"251670703","full_name":"toddsundsted/ktistec","owner":"toddsundsted","description":"Single user ActivityPub (https://www.w3.org/TR/activitypub/) server.","archived":false,"fork":false,"pushed_at":"2026-03-01T11:29:53.000Z","size":19629,"stargazers_count":424,"open_issues_count":31,"forks_count":21,"subscribers_count":11,"default_branch":"main","last_synced_at":"2026-03-02T08:42:51.650Z","etag":null,"topics":["activitypub","crystal"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/toddsundsted.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"COPYING","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":"2020-03-31T16:54:22.000Z","updated_at":"2026-02-28T01:17:44.000Z","dependencies_parsed_at":"2023-02-18T11:48:36.691Z","dependency_job_id":"69e1073b-c18e-47ef-bcd1-65a06f85ac3f","html_url":"https://github.com/toddsundsted/ktistec","commit_stats":{"total_commits":2228,"total_committers":7,"mean_commits":318.2857142857143,"dds":0.006732495511669656,"last_synced_commit":"f74267b4fbe878cfe6f0f883f99ffaeffed81b96"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"purl":"pkg:github/toddsundsted/ktistec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddsundsted%2Fktistec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddsundsted%2Fktistec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddsundsted%2Fktistec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddsundsted%2Fktistec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/toddsundsted","download_url":"https://codeload.github.com/toddsundsted/ktistec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/toddsundsted%2Fktistec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30056056,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T18:21:05.932Z","status":"ssl_error","status_checked_at":"2026-03-03T18:20:59.341Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["activitypub","crystal"],"created_at":"2024-08-01T14:00:38.635Z","updated_at":"2026-03-03T19:04:38.097Z","avatar_url":"https://github.com/toddsundsted.png","language":"Crystal","funding_links":[],"categories":["Crystal"],"sub_categories":[],"readme":"# ![Ktistec](images/logo.png)\n  - [Features](#features)\n    - [Text and images](#text-and-images)\n    - [Draft posts](#draft-posts)\n    - [Threaded replies](#threaded-replies)\n    - [Thread Analysis](#thread-analysis)\n    - [Translations](#translations)\n    - [@-mention and #-hashtag autocomplete](#-mention-and--hashtag-autocomplete)\n    - [Custom emoji](#custom-emoji)\n    - [Polls](#polls)\n    - [Control over comment visibility](#control-over-comment-visibility)\n    - [Pretty URLs](#pretty-urls)\n    - [Open Graph metadata](#open-graph-metadata)\n    - [Followers/following](#followersfollowing)\n    - [Content discovery](#content-discovery)\n    - [Content filtering](#content-filtering)\n    - [Blocking](#blocking)\n    - [Bookmarks](#bookmarks)\n    - [Pinned posts](#pinned-posts)\n    - [Quote posts](#quote-posts)\n    - [RSS feeds](#rss-feeds)\n    - [X-Ray Mode](#x-ray-mode)\n    - [Metrics](#metrics)\n    - [Tasks](#tasks)\n    - [Scripts](#scripts)\n  - [Theming](#theming)\n  - [API](#api)\n    - [A Note on ActivityPub](#a-note-on-activitypub)\n    - [Publishing an Object](#publishing-an-object)\n    - [Sharing an Object (ActivityPub `Announce`)](#sharing-an-object-activitypub-announce)\n    - [Liking an Object](#liking-an-object)\n    - [Following an Actor](#following-an-actor)\n    - [Undoing an Activity](#undoing-an-activity)\n    - [Deleting](#deleting)\n  - [MCP Support](#mcp-support)\n    - [OAuth](#oauth)\n  - [Prerequisites](#prerequisites)\n  - [Building](#building)\n    - [SQLite3 Compatibility](#sqlite3-compatibility)\n    - [Running Tests](#running-tests)\n  - [Setup, Configuration, and Usage](#setup-configuration-and-usage)\n    - [Command Line Options](#command-line-options)\n    - [User Settings](#user-settings)\n    - [Site Settings](#site-settings)\n  - [Contributors](#contributors)\n  - [Copyright and License](#copyright-and-license)\n\n**Ktistec** is an [ActivityPub](https://www.w3.org/TR/activitypub/)\nserver. It is intended for small numbers of trusted users (everyone is\nan administrator). It is designed to have few runtime dependencies --\nfor example, it uses SQLite as its database, instead of PostgreSQL +\nRedis + etc. It is licensed under the AGPLv3.\n\nKtistec powers [Epiktistes](https://epiktistes.com/), my low-volume\nhome in the Fediverse. If you want to talk to me, I'm\n[@toddsundsted@epiktistes.com](https://epiktistes.com/@toddsundsted).\n\n## Features\n\nKtistec is intended for writing and scripting.\n\n### Text and images\n\nKtistec supports two editing modes: a rich text editor and a Markdown\neditor. Choose your preferred editor in your account settings.\n\nThe **rich text editor** provides text formatting options including\n*bold*, *italic*, *strikethrough*, *code* (inline and block),\n*superscript*/*subscript*, *headers*, *blockquotes* with nested\nindentation, and both *bullet* and *numeric* lists.\n\nThe rich text editor also supports adding and editing alt text for\nimages to improve accessibility. Alt text describes images for screen\nreaders and displays when images fail to load.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/tszn70.png\" width=460\u003e\n\nThe **Markdown editor** lets you write posts in Markdown syntax.\nKtistec automatically converts Markdown to HTML when you publish. In\naddition to CommonMark syntax, Ktistec supports hashtags and mentions.\n\nKtistec supports inline placement of images, with ActivityPub image\nattachments used for compatibility with non-Ktistec servers. Images\nsupport focal point positioning to ensure the most important part of\nan image is always visible in thumbnails. Focal point positioning\ncurrently only works with federated attachments. There is no means to\nedit an image's focal point locally at this time.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/aecz36.png\" width=460\u003e\n\n### Draft posts\n\nMeaningful writing is an iterative process so Ktistec supports draft\nposts. Draft posts aren't visible until you publish them. Draft posts,\nincluding draft replies, are automatically saved as you type.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/683hld.png\" width=460\u003e\n\nWith Ktistec, you can focus on creating without worrying about losing\nyour work.\n\n### Threaded replies\n\nThreaded replies make it easier to follow discussions with many\nposts. To keep the author's first posts together, the author's\nself-replies are prioritized in the thread view.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/eaxx1q.png\" width=460\u003e\n\n### Thread Analysis\n\nKtistec can analyze large conversation threads to help you navigate\ncomplex discussions. Thread analysis provides:\n\n- **Key Participants**: Identify the most active contributors\n- **Timeline Histogram**: Visualize conversation activity over time\n- **Notable Branches**: Discover important sub-conversations within threads\n\nAccess thread analysis from the thread view page for any conversation.\n\n### Translations\n\nIntegrate Ktistec with a translation service like DeepL or\nLibreTranslate (or host your own) and translate posts in other\nlanguages into your own language.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/733yvm.gif\" width=460\u003e\n\nSee [Configuring Translation](#configuring-translation) for details on\nhow to set up the integration.\n\n### @-mention and #-hashtag autocomplete\n\nKtistec automatically converts @-mentions and #-hashtags into links,\nand to encourage hands-on-the-keyboard composition, Ktistec supports\nautocompletion.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/22aee8.gif\" width=460\u003e\n\n### Custom emoji\n\nKtistec supports viewing custom emoji in posts and on actor\nprofiles. Custom emoji are federated from remote servers and\nautomatically rendered when present.\n\n### Polls\n\nKtistec supports creating, viewing, and voting on polls. When\ncomposing a post, you can add a poll with multiple options and set an\nexpiration time. Create polls to gather feedback from your\nfollowers. When a post includes a poll, you can see the available\noptions and vote directly from your timeline. Poll results are updated\nin real-time as votes are federated across the network.  Poll authors\nreceive notifications when their polls expire.\n\n### Control over comment visibility\n\nKtistec promotes healthy dialog and gives you comprehensive control\nover content visibility.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/b70e69.png\" width=460\u003e\n\nControl which replies to your posts are public and visible to\nanonymous users, and which are private.\n\nMark posts as sensitive using the content warning checkbox in the\neditor. Sensitive posts are hidden behind a summary that readers can\nclick to reveal the content.\n\nSend private messages directly to specific users with proper\nvisibility controls.\n\n### Pretty URLs\n\nAssign pretty (canonical) URLs to posts, both for SEO and as helpful\nmnemonics for users (and yourself).\n\n### Open Graph metadata\n\nKtistec includes Open Graph metadata support for actor and posts.\nWhen you share a link to your profile or a post on social media\nplatforms, rich previews with images, titles, and descriptions are\nautomatically generated. Configure a site image in the site settings\nto customize how your instance appears when shared.\n\n### Followers/following\n\nThe Fediverse is a distributed social network. You can follow other\nusers on other servers from your timeline or by searching for them by\nname. You can also approve or deny follow requests and manually\nrefresh an actor's profile directly from their profile page. Ktistec\nis also compatible with the \"remote follow\" protocol used by Mastodon\nand other servers.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/88hvqq.png\" width=460\u003e\n\n### Content discovery\n\nIn addition to following other users, you can follow threads, hashtags\nand even mentions. When posts arrive for content you follow, a\nnotification is added to your notifications. Because running a single\nuser instance can be lonely, Ktistec also proactively (and gently)\nfetches relevant content from other servers.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/d8kf0q.png\" width=460\u003e\n\nTo make navigation and discovery easier, post details pages now have\nlabels with links to internal hashtag and mention index pages.\n\n### Content filtering\n\nContent filters prevent undesirable content from appearing in your\ntimeline and notifications. Filter terms match on the text of a post\n(ignoring any markup). Filters support wildcards.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/wzgeti.png\" width=460\u003e\n\n### Blocking\n\nKtistec gives you control over what you see. Blocking authors removes\nthem and their posts from your timeline, and obscures their handle and\ndisplay name. Blocking posts removes individual posts. The user\ninterface makes it clear when content is unavailable because it has\nbeen deleted or blocked.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/42fwx6.png\" width=460\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/epdb39.png\" width=460\u003e\n\n### Bookmarks\n\nSave posts for later with bookmarks. Bookmark any post to add it to\nyour personal bookmarks collection for easy access.\n\n### Pinned posts\n\nPin important posts to the top of your profile. Pinned posts appear\nprominently at the top of your profile page and are also exposed via\nthe Mastodon \"featured posts\" collection for compatibility with\nMastodon and other Fediverse servers.\n\n### Quote posts\n\nKtistec supports consent-respecting quote posts (FEP-044f), allowing\nyou to quote another user's post in your own post. You can also\nconfigure whether to automatically approve requests to quote your\nposts or to require manual approval.\n\n### RSS feeds\n\nKtistec provides RSS feeds for easy content syndication even without\nan account in the Fediverse. RSS feeds are available on the *home\npage* and the *account pages*.\n\n### X-Ray Mode\n\nX-Ray Mode is a developer and power-user tool for inspecting\nActivityPub JSON-LD representations of actors, objects, and other\ncontent. Press `Ctrl+Shift+X` on any page to open X-Ray Mode:\n\n- **Cached Version**: View the local JSON-LD representation stored in the Ktistec database\n- **Remote Version**: Fetch and view the original JSON-LD representation from the source server\n- **Navigation**: Click on any ActivityPub IRI to navigate to that object\n- **History**: Use Alt+Left and Alt+Right to navigate through your viewing history\n\nThis feature is useful for debugging federation issues, understanding ActivityPub\nstructures, and verifying how content is stored and represented.\n\n### Metrics\n\nKtistec tracks metrics about how the instance is performing. Metrics\ninclude inbox and outbox activity, as well as memory usage; and the\nmachinery is in place to do much more.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/vrxnmg.png\" width=460\u003e\n\n### Tasks\n\nView currently running tasks. Tasks, in Ktistec, are background jobs\nthat deliver content, fetch content, and perform other housekeeping\nchores.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/h075mm.png\" width=460\u003e\n\n### Scripts\n\nThe Ktistec server will periodically and in sequential order run all\n**executable** files in the `etc/scripts` directory.\n\nWhen running scripts, the Ktistec server will set the following\nvariables in the child process's environment:\n\n* API\\_KEY - authenticates the script with the server\n* KTISTEC\\_HOST - the base URL of the server instance\n* KTISTEC\\_NAME - the name of the server instance\n* USERNAME - the username of the account the script is run as\n\nOutput to `STDOUT` and `STDERR` will show up in the Ktistec server\nlogs.  Output to `STDOUT` will be emitted as severity `INFO` and\noutput to `STDERR` will be emitted as severity `WARN`.\n\nScripts can use the [API](#api) to communicate with the Ktistec\nserver.\n\n## Theming\n\nKtistec includes built-in theming support that allows for custom theme\ncreation. The theming system uses a hierarchy of CSS custom\nproperties and computed fallback values to provide consistent\nstyling across all elements with a minimum of effort.\n\nTheme authors can customize at multiple levels:\n\n### Level 1 - Base Colors\n\nDefine only base colors like `--text-primary`, `--bg-primary`,\n`--bg-input`, `--semantic-primary`, etc. Variations are\nautomatically generated using `color-mix` formulas.\n\nExample:\n```css\n:root { --semantic-primary: #7277cf; }\n```\n\nResult: `--bg-accent-code`, `--anchor-color`, etc. auto-generate using\n`--semantic-primary` as the base color.\n\n### Level 2 - Full Control\n\nDefine base colors and derived colors. Defined colors are used as\nspecified. The rest are automatically generated.\n\nExample:\n```css\n:root {\n  --text-primary: #333;\n  --text-primary-2: #ff0000;\n}\n```\n\nResult: `--text-primary-1/-3/-4` auto-generate; `--text-primary-2` is\nred.\n\nThe full set of CSS custom color properties is found in\n[variables.less](src/assets/css/themes/variables.less).\n\n### CSS Classes and Data Attributes\n\nKtistec exposes rich metadata through CSS classes and data attributes\nthat you can target in your themes. In the descriptions below, **author**\nrefers to the original creator of the content, while **actor** refers\nto the user performing the activity (e.g., the person who boosted the\npost).\n\n#### CSS Classes\n\n##### ActivityPub Object Types\n- `object-note`, `object-question`, etc.\n\n##### ActivityPub Actor Types\n- `actor-person`, `actor-group`, etc.\n\n##### ActivityPub Activity Types\n- `activity-create`, `activity-announce`, `activity-like`\n\n##### Object States\n- `is-draft` - Post is a draft\n- `is-deleted` - Post or author is deleted\n- `is-blocked` - Post or author is blocked\n- `is-sensitive` - Post is marked sensitive\n- `has-replies` - Post has at least one reply\n- `has-media` - Post has media attachments\n\n##### Visibility States\n- `visibility-public` - Posted to public timeline\n- `visibility-private` - Posted to followers only\n- `visibility-direct` - Direct mention\n\n##### Relationship States (when logged in)\n- `author-followed-by-me` - You follow the original author\n- `actor-followed-by-me` - You follow the announcer/liker\n\n##### Mention States\n- `mentions-me` - You are mentioned in this post\n- `mentions-only-me` - You are the only actor mentioned\n\n##### Visual States\n- `highlighted` - Post is highlighted in the UI\n- `depth-0`, `depth-1`, `depth-2`, etc. - Thread depth levels\n\n#### Data Attributes\n\nData attributes allow targeting specific identities and content identifiers.\n\n##### Author/Actor Identifiers\n- `data-author-handle` - Author's full handle (e.g., `@alice@example.com`)\n- `data-author-iri` - Author's full IRI (e.g., `https://example.com/actors/alice`)\n- `data-actor-handle` - Actor's handle (when different from author, e.g., in boosts)\n- `data-actor-iri` - Actor's IRI (when different from author)\n\n##### Content Tags\n- `data-followed-hashtags` - Space-separated hashtags you follow that appear in this post\n- `data-followed-mentions` - Space-separated mentions you follow that appear in this post\n- `data-hashtags` - Up to 10 space-separated hashtags that appear in the post\n- `data-mentions` - Up to 10 space-separated mentions that appear in the post\n\n##### Metadata\n- `data-object-id` - Database ID of the object\n\n#### Example Selectors\n\n```css\n/* Highlight posts from a specific author */\n.ui.feed [data-author-handle=\"@alice@example.com\"] {\n  border-left: 4px solid purple;\n  background: #f5f0ff;\n}\n\n/* Style posts about specific topics */\n.ui.feed [data-followed-hashtags~=\"activitypub\"] {\n  border-left-color: #6b4fbb;\n}\n\n/* Followed author shared by followed actor */\n.ui.feed .activity-announce.author-followed-by-me.actor-followed-by-me {\n  background: linear-gradient(to right, #e8f5e9, #e1f5fe);\n  border-left: 4px solid #4caf50;\n  border-top: 3px solid #00bcd4;\n}\n\n/* Polls from groups */\n.ui.feed .object-question.actor-group {\n  border-left: 5px solid #ff9800;\n}\n\n/* Direct messages from followed authors */\n.ui.feed .visibility-direct.author-followed-by-me {\n  border: 3px solid #4caf50;\n}\n```\n\n### Custom Themes\n\nYou can create custom themes by adding CSS and/or JavaScript files to\nthe `public/themes/` directory. These files are automatically added to\nevery page's HTML `head` element.\n\n#### Creating a Custom Theme\n\n1. Add `.css` and `.js` files to `public/themes/`\n2. Use CSS custom properties to override theme variables\n3. Restart the server—theme files are discovered at startup\n\nA simple custom theme that supports both light and dark modes:\n\n```css\n:root {\n  --text-primary: #2c3e50;\n  --bg-primary: #ecf0f1;\n  --semantic-primary: #e74c3c;\n  --anchor-color: #e67e22;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --text-primary: #ecf0f1;\n    --bg-primary: #2c3e50;\n    --semantic-primary: #e74c3c;\n    --anchor-color: #f39c12;\n  }\n}\n```\n\n### Light/Dark Mode Support\n\nIf themes are properly designed, Ktistec automatically adapts to your\nbrowser's light/dark mode preference.\n\n## API\n\nGiven a valid `API_KEY`, scripts and external tools have full access\nto the Ktistec server's API.  This allows them to automate actions on\nthe server such as posting, following, sharing, liking, etc.\n\nThe table below contains a list of supported endpoints:\n\n| Method | Path | Notes |\n|-|-|-|\n| GET    | /actors/:username/inbox     | Retrieves a page of `activities` in your inbox as an ActivityPub collection. |\n| GET    | /actors/:username/outbox    | Retrieves a page of `activities` in your outbox as an ActivityPub collection. |\n| POST   | /actors/:username/outbox    | Puts an `activity` in your outbox for delivery. |\n| GET    | /actors/:username/posts     | Retrieves a page of `objects` you've published. |\n| GET    | /actors/:username/drafts    | Retrieves a page of `objects` you've saved as drafts. |\n| GET    | /actors/:username/followers | Retrieves a page of `actors` following you. |\n| GET    | /actors/:username/following | Retrieves a page of `actors` you're following. |\n| GET    | /admin/accounts             | Retrieves all user accounts. |\n| GET    | /admin/accounts/new         | Gets a representation of an account. |\n| POST   | /admin/accounts             | Creates a new user account. |\n| POST   | /settings/actor             | Updates account settings for the authenticated user. |\n| GET    | /lookup/activity?iri=:iri   | Looks up the `activity` in the server cache identified by `iri`. |\n| GET    | /lookup/actor?iri=:iri      | Looks up the `actor` in the server cache identified by `iri`. |\n| GET    | /lookup/object?iri=:iri     | Looks up the `object` in the server cache identified by `iri`. |\n| GET    | /sessions                   | Gets a representation of an authentication attempt with unset `username` and `password`. |\n| POST   | /sessions                   | Authenticates using the supplied `username` and `password`. |\n| DELETE | /sessions                   | Destroys the current session. |\n\nThe `/sessions` endpoint is useful when doing script development\noutside of the server environment.  The scripts the Ktistec server\nruns directly do not need to create their own session -- the supplied\n`API_KEY` is sufficient.\n\n### Initial Server Configuration\n\nYou can configure a new Ktistec server entirely via the API.\n\n#### Configure Server Settings\n\nFirst, configure the server host name and site name (no authentication\nrequired):\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"host\":\"https://your-domain.com\",\"site\":\"Your Site Name\"}' \\\n  \"$KTISTEC_HOST/\"\n```\n\n#### Create Primary User Account\n\nNext, create the primary user account (again, no authentication\nrequired).\n\n| Name                   | Notes |\n|-|-|\n| username               | The username for the primary account. |\n| password               | The password for the primary account. |\n| name                   | Optional. Display name for the account. |\n| summary                | Optional. Biography/description for the account. |\n| language               | IETF BCP 47 language tag (e.g., \"en\", \"en-US\"). |\n| timezone               | IANA timezone (e.g., \"UTC\", \"America/New_York\"). |\n| auto_approve_followers | Optional. When `true`, follow requests are automatically approved. Defaults to `false`. |\n| auto_follow_back       | Optional. When `true`, automatically follows back accounts that follow you. Defaults to `false`. |\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"your_username\",\"password\":\"YourSecurePassword123@\",\"name\":\"Your Display Name\",\"summary\":\"Your summary\",\"language\":\"en\",\"timezone\":\"UTC\",\"auto_approve_followers\":true,\"auto_follow_back\":false}' \\\n  \"$KTISTEC_HOST/\"\n```\n\n### Admin Account Management\n\n#### List All Accounts\n\nRetrieve all user accounts:\n\n```bash\ncurl -s \\\n  -X GET \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Accept: application/json\" \\\n  \"$KTISTEC_HOST/admin/accounts\"\n```\n\n#### Create New Account\n\nCreate a new user account. Supports the same parameters as creating the primary account.\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"another_user\",\"password\":\"AnotherPassword123@\",\"name\":\"Another User\",\"summary\":\"Another user account\",\"language\":\"en\",\"timezone\":\"UTC\",\"auto_approve_followers\":false,\"auto_follow_back\":true}' \\\n  \"$KTISTEC_HOST/admin/accounts\"\n```\n\n### Account Settings\n\n#### Update Actor Account Settings\n\nUpdate account settings for the authenticated user. Requires authentication.\n\n| Name                   | Notes |\n|-|-|\n| name                   | Optional. Display name for the account. |\n| summary                | Optional. Biography/description for the account. |\n| language               | Optional. IETF BCP 47 language tag (e.g., \"en\", \"en-US\"). |\n| timezone               | Optional. IANA timezone (e.g., \"UTC\", \"America/New_York\"). |\n| password               | Optional. New password for the account. |\n| auto_approve_followers | Optional. When `true`, follow requests are automatically approved. When `false`, requests require manual approval. |\n| auto_follow_back       | Optional. When `true`, automatically follows back accounts that follow you. |\n| image                  | Optional. Full URI of the account's profile image. |\n| icon                   | Optional. Full URI of the account's avatar icon. |\n| attachments            | Optional. Array of attachment objects. |\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Updated Display Name\",\"summary\":\"Updated summary\",\"language\":\"en\",\"timezone\":\"UTC\"}' \\\n  \"$KTISTEC_HOST/settings/actor\"\n```\n\n### A Note on ActivityPub\n\nAt its core, Ktistec is an ActivityPub server and some things about\nits API make more sense if you understand a little bit about\nActivityPub.\n\nActivityPub is a protocol for sending and receiving messages.\nEntities called `actors` send and receive `activities`.  `Activities`\nrepresent the various social actions that define interaction on the\nFediverse: posting, sharing and liking posts, following other actors,\netc.\n\nEvery `actor` has an `inbox` for receiving `activities` from the\n`actors` they follow, and an `outbox` for sending `activities` to the\n`actors` that follow them.\n\nA Fediverse \"post\", \"status\", \"note\", \"toot\", etc. are all names for\nan ActivityPub `object`.  ActivityPub `activities` are typically about\n`objects`.\n\nActivityPub `actors`, `activities`, and `objects` are described using\na flavor of JSON known as JSON-LD.  Every `actor`, `activity`, and\n`object` is identified by a unique `IRI`.\n\nKtistec allows a JSON shorthand (described below) to be used when\npublishing, which greatly simplifies the development of scripts.\n\n* [ActivityPub W3C Recommendation](https://www.w3.org/TR/activitypub/)\n\n### Publishing an Object\n\nTo publish an `object`, `POST` a JSON `activity` to the `outbox`\nendpoint (examples assume scripts use `curl`). The JSON `activity` may\ninclude the following fields:\n\n| Name        | Notes |\n|-|-|\n| type        | Must be \"Publish\". |\n| content     | An HTML formatted string. |\n| name        | Optional. A plain text string often displayed as the title of a post. |\n| summary     | Optional. A plain text string often used as a summary of a longer post. |\n| object      | Optional. The IRI of the `object` being updated (instead of created). |\n| in-reply-to | Optional. The IRI of the `object` being replied to. |\n| to          | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |\n| cc          | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |\n| audience    | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |\n| visibility  | Optional. Controls post visibility. Values: \"public\", \"private\", \"direct\". |\n| sensitive   | Optional. May be `true` or `false`. Marks content as sensitive. |\n\nBy default, `cc` includes the publishing `actor`'s followers collection.\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Publish\",\"content\":\"this is a test\",\"visibility\":\"public\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\nNote: \"publishing\" an `object` encompasses both creating a new\n`object` (an ActivityPub `Create` activity) and updating an existing\n`object` (an ActivityPub `Update` activity).\n\n### Sharing an Object (ActivityPub `Announce`)\n\nTo share an `object`, `POST` a JSON `activity` to the `outbox`\nendpoint. The JSON `activity` may include the following fields:\n\n| Name       | Notes |\n|-|-|\n| type       | Must be \"Announce\". |\n| object     | The IRI of the `object` being shared. |\n| to         | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |\n| cc         | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |\n| audience   | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |\n| visibility | Optional. Controls post visibility. Values: \"public\", \"private\", \"direct\". |\n\nBy default, `to` includes the actor to which the `object` is\nattributed, and `cc` includes the publishing `actor`'s followers\ncollection.\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Announce\",\"object\":\"https://example.com/objects/123\",\"visibility\":\"public\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n### Liking an Object\n\nTo like an `object`, `POST` a JSON `activity` to the `outbox`\nendpoint. The JSON `activity` may include the following fields:\n\n| Name       | Notes |\n|-|-|\n| type       | Must be \"Like\". |\n| object     | The IRI of the `object` being liked. |\n| to         | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |\n| cc         | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |\n| audience   | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |\n| visibility | Optional. Controls post visibility. Values: \"public\", \"private\", \"direct\". |\n\nBy default, `to` includes the actor to which the `object` is\nattributed, and `cc` includes the publishing `actor`'s followers\ncollection.\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Like\",\"object\":\"https://example.com/objects/123\",\"visibility\":\"public\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n### Disliking an Object\n\nTo dislike an `object`, `POST` a JSON `activity` to the `outbox`\nendpoint. The JSON `activity` may include the following fields:\n\n| Name       | Notes |\n|-|-|\n| type       | Must be \"Dislike\". |\n| object     | The IRI of the `object` being disliked. |\n| to         | Optional. A comma-separate list of `actors` to address. Specified as IRIs. |\n| cc         | Optional. A comma-separate list of `actors` to CC. Specified as IRIs. |\n| audience   | Optional. A comma-separate list of `actors` in the audience. Specified as IRIs. |\n| visibility | Optional. Controls post visibility. Values: \"public\", \"private\", \"direct\". |\n\nBy default, `to` includes the actor to which the `object` is\nattributed, and `cc` includes the publishing `actor`'s followers\ncollection.\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Dislike\",\"object\":\"https://example.com/objects/123\",\"visibility\":\"public\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n### Following an Actor\n\nTo follow an actor, `POST` a JSON `activity` to the `outbox`\nendpoint. The JSON `activity` may include the following fields:\n\n| Name   | Notes |\n|-|-|\n| type   | Must be \"Follow\". |\n| object | The IRI of the `actor` being followed. |\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Follow\",\"object\":\"https://example.com/actors/alice\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n### Undoing an Activity\n\nYou can undo a previously published `activity` by `POST`ing an `Undo`\n`activity` to the `outbox` endpoint. The JSON `activity` may include\nthe following fields:\n\n| Name   | Notes |\n|-|-|\n| type   | Must be \"Undo\". |\n| object | The IRI of the `activity` being undone. |\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Undo\",\"object\":\"https://example.com/activities/123\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n### Deleting\n\nYou can delete an `actor` or `object` by `POST`ing a `Delete`\n`activity` to the `outbox` endpoint. The JSON `activity` may include\nthe following fields:\n\n| Name   | Notes |\n|-|-|\n| type   | Must be \"Delete\". |\n| object | The IRI of the `actor` or `object` being deleted. |\n\nExample:\n\n```bash\ncurl -s \\\n  -X POST \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"Delete\",\"object\":\"https://example.com/123\"}' \\\n  \"$KTISTEC_HOST/actors/$USERNAME/outbox\"\n```\n\n## MCP Support\n\nKtistec provides Model Context Protocol (MCP) server support for\nintegrating Ktistec with MCP clients. Compatible clients should \"just\nwork\" with Ktistec.\n\n**Important:** *Content generation is not currently supported.* There\nare technical issues to resolve, but for the most part I currently\nprioritize accessibility and analytics use cases.\n\n### OAuth\n\nOAuth2 authentication is required to access the MCP server from a\ncompatible MCP client.\n\n### Prompts\n\n#### server_info\n\nDemonstrates how to use the MCP server to retrieve and present\ndetailed information about the Ktistec server.\n\n#### whats_new\n\nDemonstrates how to use the MCP server to retrieve and summarize\nrecent posts and social activity.\n\n### Resources\n\n#### ktistec://information\n\nKtistec server instance information including version, collections\nsupported, statistics, etc.\n\n#### ktistec://users/{id}\n\nUser account information for registered users. Returns account details\nincluding username, language, timezone, and related actor information.\nSupports a single ID (`ktistec://users/123`).\n\n#### ktistec://actors/{id*}\n\nActivityPub actor profiles including name, summary, icon, image,\nattachments/metadata, and URLs. Supports single ID (`ktistec://actors/123`) or\ncomma-separated IDs for batch retrieval (`ktistec://actors/123,456,789`).\n\n#### ktistec://objects/{id*}\n\nActivityPub objects (posts) including name, summary, content,\nattachments, and relationships. Supports single ID (`ktistec://objects/123`) or\ncomma-separated IDs for batch retrieval (`ktistec://objects/123,456,789`).\n\n### Tools\n\n#### count\\_collection\\_since(name, since)\n\nCount items in ActivityPub collections since a given time. Use this\ntool when you want to know if new items have been added in the last\nday/week/month.\n\n**Parameters:**\n- `name` (string, required) - Name of the collection to count.\n- `since` (string, required) - RFC3339 timestamp to count from (e.g., `2024-01-01T00:00:00Z`)\n\n#### paginate\\_collection(name, page, size)\n\nPaginate through collections of ActivityPub items. Use this tool when\nyou want to retrieve the items in a collection.\n\n**Parameters:**\n- `name` (string, required) - Name of the collection to paginate\n- `page` (integer, optional) - Page number (defaults to 1, minimum 1)\n- `size` (integer, optional) - Number of items per page (defaults to 10, minimum 1, maximum 20)\n\n#### read\\_resources(uris)\n\nRead one or more resources by URI. Supports all resource types\nincluding templated resources (actors, objects) and static resources\n(information, users). Supports batched reads with comma-separated\nIDs.\n\nUse this tool as a universal fallback when resources are not supported\nby an MCP client.\n\n**Parameters:**\n- `uris` (array, required) - Resource URIs to read (e.g., `['ktistec://actors/123,456', 'ktistec://objects/456,789']`)\n\n#### analyze\\_thread(object\\_id)\n\nAnalyze a conversation thread to understand its structure and activity.\nReturns key participants, timeline histogram, and notable branches.\n\n**Parameters:**\n- `object_id` (integer, required) - Database ID of any object in the thread\n\n#### get\\_thread(object\\_id, projection, page\\_size) or get\\_thread(cursor)\n\nRetrieve thread structure, metadata, and summary data for a\nconversation thread with pagination support. Supports two modes of\noperation: initial query mode and pagination mode.\n\n**Initial query mode** Return summary data and the first page of\nobjects. If there are more pages, the results include a `cursor`.\n\n**Pagination mode**: Provide only the `cursor` to fetch the next page.\nReturns the next page of objects and a new `cursor` if more pages\nremain.\n\n**Parameters:**\n- `object_id` (integer) - Database ID of any object in the thread\n- `projection` (string) - Data fields to include: 'minimal' (IDs and structure only) or 'metadata' (adds authors, timestamps)\n- `page_size` (integer) - Number of objects per page\n- `cursor` (string) - Opaque pagination cursor\n\n### Supported Collections\n\n  - Standard: `timeline`, `notifications`, `posts`, `drafts`, `likes`, `announces`, `bookmarks`, `pins`, `followers`, `following`\n  - Dynamic: `hashtag#\u003cname\u003e` (e.g., `hashtag#technology`), `mention@\u003cname\u003e` (e.g., `mention@euripides`)\n\n### Compatibility\n\nThe Ktistec MCP server works well with Claude Desktop and Claude\nCode. I have also tested it with Gemini CLI and Cursor. It also works\nwith open source tools like Goose and Ollama.\n\n## Prerequisites\n\nTo run an instance of Ktistec as part of the Fediverse, you'll need a\nserver with a fixed hostname. In the Fediverse, users are identified\nby (and content is addressed to) a username and a hostname.\n\n## Building\n\nYou must compile the Ktistec server executable from its source code.\nYou will need to install a recent release of the [Crystal programming\nlanguage](https://crystal-lang.org/install/). Ktistec requires at least\nSQLite3 version 3.35.0 (but see notes on [Sqlite3 compatibility](#sqlite3-compatibility)).\n\nTo obtain the source code, clone the [Ktistec Github\nrepo](https://github.com/toddsundsted/ktistec).\n\nIf you intend to do *development* on the server, check out the\n**main** branch. In addition to Crystal, you'll also need Node.js and\nWebpack to build the JS and CSS assets from source.\n\nIf you just want to build and run the server, check out the **dist**\nbranch.\n\nTo compile the server:\n\n`$ crystal build src/ktistec/server.cr`\n\nIf you're developing on the **main** branch, build the assets next\n(skip this step if you're on the **dist** branch--the latest JS and\nCSS assets are already built for you):\n\n`$ npm run build`\n\nRun the compiled executable:\n\n`$ LOG_LEVEL=INFO ./server`\n\nThe first time the server runs it will run the database migrations\nnecessary to create the database. This should only take a few seconds,\nmaximum. When the server is ready to accept connections you will see\nsomething like:\n\n`Ktistec is ready to lead at http://0.0.0.0:3000`\n\nYou can now connect to and configure the server.\n\n### SQLite3 Compatibility\n\nThe following SQLite3 versions are known to have bugs that cause\nproblems for Ktistec:\n\n| SQLite Version | Issue |\n|--|--|\n| 3.39.x | problems with bloom filters and recursive queries [link](https://sqlite.org/forum/forumpost/56de336385) |\n| 3.40.x | problems with bloom filters and recursive queries [link](https://sqlite.org/forum/forumpost/56de336385) |\n\n### Running Tests\n\nIf you change the code, you should run the tests:\n\n`$ crystal spec`\n\n## Setup, Configuration, and Usage\n\nThe server runs on port 3000. If you're planning on running it in\nproduction, you should put Nginx or Apache in front of it. Don't\nforget an SSL certificate!\n\nWhen you run Ktistec for the first time, you'll need to name the\nserver and create the primary user.\n\nKtistec needs to know the *host name* of its home on the internet.\nThe server host name is a part of every users's identity in the\nFediverse. My identity is \"toddsundsted@epiktistes.com\". The host name\nis \"epiktistes.com\" and other federated servers, and users, know to\nsend posts and other content to me there.\n\nGive the server a *site name*, too.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/kl3yf6.png\" width=640\u003e\n\nAfter you name the server, you create the primary user account. The\nprimary user can create additional user accounts via the admin\ninterface at `/admin/accounts`. All additional users are also\nadministrators.\n\nAt a minimum, you need to specify the user's *username*, *password*,\n*language*, and *timezone*. You can use a single character for the\n*username* if you want, but you'll need six characters, including\nletters, numbers and symbols, for the *password* (but more than six\ncharacters is better). *language* is any valid IETF BCP 47 language\ntag (e.g. \"en-US\" or \"en\").  *timezone* is any valid IANA time zone\ndatabase string (e.g. \"America/New_York\").\n\nYou can select an ActivityPub actor type. \"Person\" is the\ndefault. Other options are \"Application\", \"Group\", \"Organization\",\nand \"Service\".\n\n*Display name* and *summary* are optional.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/mq4ntx.png\" width=640\u003e\n\nOnce these steps are done, you're running!\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/o0ton2.png\" width=640\u003e\n\n### Command Line Options\n\nThe Ktistec server supports several command line options:\n\n#### `--full-garbage-collection-on-startup`\n\n**⚠️ WARNING: This operation can take significant time and delete large amounts of data.**\n\nGarbage collection removes old ActivityPub objects that are not\nconnected to your user through:\n\n- **Attribution**: Objects attributed to you or actors you follow\n- **Activities**: Objects referenced by your activities or activities of actors you follow\n- **Collections**: Objects in your timeline, notifications, or outbox\n- **Content**: Objects with hashtags, mentions, or in threads you follow\n\nNote that garbage collection preserves:\n\n- Complete threads if any object in a thread is preserved\n- Objects newer than the configured age limit (default: 365 days)\n\nIt also:\n\n- Performs database optimization (VACUUM and PRAGMA optimize) after cleanup\n- May take minutes to hours, depending on database size\n- Should not be interrupted once started\n\n**Consider backing up your database before using this option!**\n\n### User Settings\n\nKtistec provides comprehensive user/account settings that you can\naccess through the web interface. Navigate to the settings page to\nconfigure your settings.\n\n**Available Settings:**\n\n- **Display Name**: Your public display name shown on your profile\n- **Summary**: Your biography or profile description\n- **Language**: IETF BCP 47 language tag (e.g., \"en-US\", \"de\", \"ja\") used for your posts and UI preferences\n- **Timezone**: IANA timezone (e.g., \"America/New_York\", \"UTC\") for displaying timestamps and aggregating metrics\n- **Password**: When supplied, updates your account password\n- **Auto-approve Followers**: When enabled, follow requests are automatically approved without requiring manual review\n- **Auto-follow Back**: When enabled, automatically follows back actors that follow you\n- **Manually Approve Quote Requests**: When enabled, quote requests require your approval before approval to quote to granted\n- **Default Editor**: Choose between the rich text editor or Markdown editor for composing posts\n- **Background Image**: Your profile banner/background image\n- **Profile Image**: Your profile avatar icon\n- **Metadata**: Up to four custom profile metadata fields with name/value pairs\n\nThese settings can also be configured via the API (see [Account Settings](#account-settings)\nin the API documentation).\n\n### Site Settings\n\nSite settings control server-wide configuration. You can access these\nfrom the settings page.\n\n**Available Settings:**\n\n- **Site Name**: The name of your Ktistec instance\n- **Site Image**: An image used for Open Graph metadata when sharing your instance on social media\n- **Description**: A customizable description displayed on your home page\n- **Footer**: A customizable footer displayed at the bottom of every page\n- **Translator Service**: Choose translation service (DeepL or LibreTranslate) if API keys are configured\n- **Translator Service URL**: API endpoint for the selected translation service\n\n#### Site Description\n\nKtistec supports a customizable site description on your home\npage.\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/6j98as.png\" width=640\u003e\n\nEdit your site description using the same rich text editor you use for\nauthoring posts:\n\n1. Visit Site Settings on the settings page\n2. Use the rich text editor to change the site description\n3. Press Update\n\n#### Translation\n\nFirst, ensure you've set your language in Account Settings on\nthe settings page. Your  language must be a valid [IETF BCP 47\nlanguage tag](https://en.wikipedia.org/wiki/IETF_language_tag) like\n\"en-US\" for US English, \"de\" for German (Deutsch), or \"ja\" for\nJapanese (日本語).\n\nIn order to enable translation, you need an *API key* for either\n[DeepL](https://www.deepl.com/) or [LibreTranslate](https://libretranslate.com/).\nThese are the only services Ktistec supports at this time.\n\nDepending on the service you use, you need to set either the\nenvironment variable `DEEPL_API_KEY` or `LIBRETRANSLATE_API_KEY`\nto the API key and restart Ktistec.\n\nOnce the API key is set correctly, Site Settings on the settings page\nwill show an input for the translator service API endpoint. Currently,\nsupported values are:\nhttps://api.deepl.com/v2/translate (for the DeepL paid service) or\nhttps://api-free.deepl.com/v2/translate (for the DeepL free service), or\nhttps://libretranslate.com/translate (for the LibreTranslate paid\nservice).\n\n\u003cimg src=\"https://raw.githubusercontent.com/toddsundsted/ktistec/main/images/2i73hd.png\" width=640\u003e\n\n*If you change the service API or change the service you are using, you\nwill need to restart Ktistec.*\n\nPosts from properly configured accounts on supported servers, like\nMastodon, include the language the content is written in. On these\nposts, Ktistec will display a button to translate the content if the\nlanguage differs from your default language.\n\nA few caveats:\n\n* Some ActivityPub servers don't explicitly support language.\n* Some users don't correctly set their posts' language.\n\nTL;DR It's a big Fediverse so translation won't always work.\n\n## Contributors\n\n- [Todd Sundsted](https://github.com/toddsundsted) - creator and maintainer\n\n## Copyright and License\n\nKtistec ActivityPub Server\nCopyright (C) 2021, 2022, 2023, 2024, 2025, 2026 Todd Sundsted\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as\npublished by the Free Software Foundation, either version 3 of the\nLicense, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\nAffero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public\nLicense along with this program. If not, see\n(https://www.gnu.org/licenses/).\n\n### LibreJS Compliance\n\nKtistec is compliant with GNU LibreJS. All JavaScript bundled with\nKtistec is free software. A complete list of JavaScript and Crystal\ndependencies with their licenses is available at `/license` on any\nrunning Ktistec instance.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoddsundsted%2Fktistec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoddsundsted%2Fktistec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoddsundsted%2Fktistec/lists"}