diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..90a4567 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: docs + +# Builds the user documentation site on every pull request, and publishes it to +# GitHub Pages on each push to develop. Zensical is the version pinned in +# uv.lock, installed by `mise run docs:build`. +on: + pull_request: + push: + branches: [develop] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 + - run: mise run docs:build + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + with: + path: site + + deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5 + id: deployment diff --git a/.gitignore b/.gitignore index a7e2ed4..05280d6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ __pycache__/ # Local server data /data/ .env + +# Documentation site, built by `mise run docs:build` +/site/ diff --git a/AGENTS.md b/AGENTS.md index 4ea3c72..84ef7f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,15 @@ decision records, documentation, code, comments and commit messages. The only exception is user-facing memory content, whose language is a store setting (for example French for the `accounting-firm` preset's synthetic data). +## User documentation + +The user documentation site is built from `docs/` with Zensical +(`zensical.toml`, `mise run docs:build`) and published to GitHub Pages from +`develop`. A milestone is not delivered until the site documents what it adds. +Each task that changes user-visible behaviour (a command, an option, an +output, an installation or upgrade step, a connector flow) updates its page in +the same change, and a new page goes into the `nav` of `zensical.toml`. + ## Tests Tests are either unit or integration tests, and live in the matching folder: diff --git a/README.md b/README.md index ad0bcef..a984d35 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,12 @@ engagements. With tiroir, the organisation of memory is configuration: you declare your drawers, and tiroir handles storage, indexing, retrieval, context injection and sync. -> **Status: design phase.** Nothing is usable yet. The requirements are being -> written; code follows once the design is approved. +**Documentation: [ancelin-code.github.io/tiroir](https://ancelin-code.github.io/tiroir/)** + +> **Status: 0.1.0.** One server for a team, run with Docker Compose behind +> Caddy, administered from the command line, and connected to Claude Desktop +> with tools to search, read and log memory. More surfaces follow, milestone +> by milestone. ## Principles @@ -29,22 +33,37 @@ injection and sync. - **Humans own the synthesis.** Logs are append-only, and summaries change only after you confirm. +## Quickstart + +On a VPS with Docker Compose and a domain name pointing to it: + +```sh +git clone https://github.com/ancelin-code/tiroir.git && cd tiroir +cp .env.example .env # set TIROIR_DOMAIN and TIROIR_ACME_EMAIL +docker compose pull && docker compose up -d +docker compose exec tiroir tiroir admin init +``` + +Then follow the documentation: + +- [Install tiroir on a VPS](https://ancelin-code.github.io/tiroir/install/vps/): + the stack, the first administrator, a member and a store. +- [Connect Claude Desktop](https://ancelin-code.github.io/tiroir/connect/claude-desktop/): + add the server as a custom connector and sign in. +- [CLI reference](https://ancelin-code.github.io/tiroir/reference/cli/): the + administration commands. + ## Planned surfaces -- An MCP server for Claude Desktop and Claude Code. -- A Claude Code adapter with automatic context injection. +- A member CLI and the Claude Code plugin with automatic context injection. - A guided setup skill that interviews you and designs your schema. -- A CLI for setup, indexing, search, validation, sync, migration and erasure. -- Presets for developers and accounting firms, with synthetic data. +- Presets beyond accounting firms, starting with developers. -## Install +## Contributing -- [Install tiroir on a VPS](docs/install/vps.md): the Compose stack behind - Caddy, the first administrator, a member and a store from a preset. -- [Connect Claude Desktop](docs/connect/claude-desktop.md): add the server as - a custom connector and sign in. -- [Skeleton exit check](docs/milestones/skeleton-exit-check.md): the checklist - run against a real server at the end of the first milestone. +`mise run check` runs every check, `mise run stack` builds the image from the +working tree and starts the stack, and `mise run docs:serve` previews the +documentation site. ## Project documents @@ -52,6 +71,8 @@ injection and sync. - [Requirements](delivery/prd.md): what v1 must do, checkably. - [Prior art review](docs/research/prior-art.md): what we learned from existing agent memory projects. +- [Skeleton exit check](docs/milestones/skeleton-exit-check.md): the checklist + run against a real server at the end of the first milestone. ## Licence diff --git a/delivery/tasks/docs-001.md b/delivery/tasks/docs-001.md new file mode 100644 index 0000000..bcc4e92 --- /dev/null +++ b/delivery/tasks/docs-001.md @@ -0,0 +1,76 @@ +--- +id: docs-001 +title: Publish the user documentation as a Zensical site on GitHub Pages +type: docs +epic: docs +depends_on: [] +parallel: true +conflicts_with: [] +tier: build +review: [] +--- + +## Context + +The 0.1.0 release works end to end, and its guides (`docs/install/vps.md`, +`docs/connect/claude-desktop.md`) sit as loose Markdown. Users need a site, and +every later milestone must update it. Delivery Kit's launch plan picked +Zensical on GitHub Pages with the `llms.txt` output; follow the same choice. +The requirements are in [the PRD](../prd.md). + +## Scope + +Files this task is expected to touch: + +- `zensical.toml` (site configuration and navigation) +- `docs/index.md` (home page: what tiroir is, who it is for, where to start) +- `docs/install/vps.md` (the image is now published: pull it instead of + building, in the start and upgrade steps; `TIROIR_TAG` takes `v0.1.0` style + tags or `latest`) +- `docs/connect/claude-desktop.md` (only what the site needs, such as links) +- `docs/reference/cli.md` (user-facing CLI reference, written for users from + `delivery/contracts/cli.md`, not copied from it) +- `.github/workflows/docs.yml` (build on pull requests, deploy to GitHub Pages + from `develop`) +- `pyproject.toml`, `uv.lock` (Zensical as a pinned dev dependency, with its + licence checked by `mise run licenses`) +- `mise.toml` (`docs:build` and `docs:serve` tasks) +- `README.md` (slim: pitch, quickstart, link to the site; fix the stale + "design phase, nothing is usable yet" line) +- `AGENTS.md` (a short rule: a milestone is not delivered until the site + documents what it adds; each task that changes user-visible behaviour + updates its page) +- `delivery/tasks/docs-001.md` + +Out of scope: + +- ADRs, contracts, the brief, research notes and milestone notes stay in the + repository and out of the site's navigation. +- A custom domain, search plugins beyond Zensical's defaults, a theme redesign. +- Documenting features that do not exist yet. + +## Acceptance criteria + +- `mise run docs:build` builds the site with no warning, and the output holds an + `llms.txt`. +- The navigation holds exactly: Home, Install (VPS), Connect (Claude Desktop), + Reference (CLI). +- `docs/install/vps.md` no longer says that no image is published, and starts + and upgrades the stack with `docker compose pull` and `up -d`, with + no `--build`; `compose.yaml` still builds locally for contributors. +- The CLI reference lists every command of the CLI contract with its arguments + and an example, and matches the real `tiroir --help` output. +- The workflow builds on pull requests and deploys to GitHub Pages only on push + to `develop`; Zensical is pinned to an exact version. +- The README links to the site and to nothing that moved without a working + target. +- `AGENTS.md` states the per-milestone documentation rule. +- `mise run check` and `mise run licenses` pass. + +## Validation + +```sh +mise run docs:build +mise run check +mise run licenses +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e3eaf95 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,40 @@ +# tiroir + +**Configurable, scoped memory for AI agents.** *Tiroir* is French for *drawer*. + +tiroir gives AI agents a memory organised the way their users actually work. A +developer thinks in repositories, an accountant in client files, a consultant +in engagements. With tiroir, that organisation is configuration: a store holds +drawers, and tiroir handles storage, indexing and retrieval. Memory is plain +Markdown in a Git repository on your own server; nothing leaves it. + +## Who it is for + +- **Administrators** who run one tiroir server for themselves or their team, on + a VPS with Docker Compose. +- **Members** of that team who connect Claude Desktop to the server, then ask + Claude to search their memory and to log what happened. + +## What version 0.1.0 does + +- One server, started with Docker Compose behind Caddy, which obtains the HTTPS + certificate by itself. The image is published for `amd64` and `arm64`. +- Members, personal and team stores, and read or write access, managed with + the [administration CLI](reference/cli.md) inside the container. +- A store starts from a preset. The one bundled preset, `accounting-firm`, + comes with invented client drawers to try the server on. +- Claude Desktop connects as a custom connector, signs in with OAuth, and gets + four tools: search, read, log, and the list of the stores the member can + reach. + +## Where to start + +1. [Install tiroir on a VPS](install/vps.md): the stack, the first + administrator, a member and a store. +2. [Connect Claude Desktop](connect/claude-desktop.md): add the server as a + custom connector and sign in. +3. [CLI reference](reference/cli.md): every administration command, with its + arguments and an example. + +tiroir is open source under the Apache-2.0 licence; its code is on +[GitHub](https://github.com/ancelin-code/tiroir). diff --git a/docs/install/vps.md b/docs/install/vps.md index 16638c0..0e83275 100644 --- a/docs/install/vps.md +++ b/docs/install/vps.md @@ -22,8 +22,10 @@ database server and sends no e-mail. ## 1. Get the stack -The stack is three files: `compose.yaml`, `deploy/Caddyfile` and `.env`. Clone -the repository on the VPS: +The stack is three files: `compose.yaml`, `deploy/Caddyfile` and `.env`. The +tiroir image itself is published at `ghcr.io/ancelin-code/tiroir`, for `amd64` +and `arm64`, so nothing is built on the VPS. Clone the repository on the VPS to +get the three files: ```sh git clone https://github.com/ancelin-code/tiroir.git @@ -42,18 +44,18 @@ Edit `.env`: |---|---| | `TIROIR_DOMAIN` | Your domain name, without `https://`. The server's public address, `https://`, is also its OAuth issuer, so it cannot change later without every member signing in again. Use `localhost` to try the stack on your own machine. | | `TIROIR_ACME_EMAIL` | The e-mail address given to the certificate authority. | -| `TIROIR_TAG` | The tag given to the image built in step 3. Leave `latest`. | +| `TIROIR_TAG` | The image tag to run: `latest` follows the newest release, a release tag such as `v0.1.0` stays on that release until you change it. | | `TIROIR_PROXY_SUBNET` | The private subnet of the stack's network, where Caddy runs. Leave the default unless it clashes with a network of yours. | ## 3. Start the stack ```sh -docker compose up -d --build +docker compose pull +docker compose up -d ``` -No tiroir image is published yet, so Compose builds it from the cloned -repository (a few minutes on the first run), pulls Caddy, then starts both. -Check that they run and that the server answers through Caddy: +Compose pulls the tiroir image for the tag in `.env` and the Caddy image, then +starts both. Check that they run and that the server answers through Caddy: ```sh docker compose ps @@ -122,12 +124,14 @@ To force it, run `docker compose exec tiroir tiroir admin index`. ## 7. Upgrade -In this version, an upgrade fetches the new code, rebuilds the image and -restarts the stack: +An upgrade updates the stack files, pulls the new image and restarts the +stack. With `TIROIR_TAG=latest`, this moves to the newest release; with a +release tag, change `TIROIR_TAG` in `.env` to the new release first. ```sh git pull -docker compose up -d --build +docker compose pull +docker compose up -d ``` The server migrates its database at start, and logs a warning if the database diff --git a/docs/reference/cli.md b/docs/reference/cli.md new file mode 100644 index 0000000..7b7dc0d --- /dev/null +++ b/docs/reference/cli.md @@ -0,0 +1,254 @@ +# CLI reference + +tiroir is administered with one command, `tiroir`, that ships inside the +server's image. In version 0.1.0 it holds the **administration CLI**, +`tiroir admin`, which you run in the server's container from the directory of +the stack: + +```sh +docker compose exec tiroir tiroir admin [options] +``` + +It works directly on the server's data, so it needs no token: shell access to +the container is the administrator's credential. A change to members or access +takes effect at the members' next request. + +Every command accepts `-h` or `--help`, which prints its usage. The examples +below leave out the `docker compose exec tiroir` prefix. + +!!! note "Coming later" + + A member CLI, run on a member's own machine against the server (search, + read, log, proposals), and the administration commands for embeddings, + mirrors, evaluation, backups and the server's state are planned for later + releases. They are not in 0.1.0, and this page will document them when they + are. + +## `tiroir` + +```text +usage: tiroir [-h] [--version] {admin} ... +``` + +| Option | Meaning | +|---|---| +| `--version` | Print the version, for example `0.1.0`, and exit. | + +## `tiroir admin init` + +```text +usage: tiroir admin init [-h] +``` + +Creates the first administrator, handle `admin`, and prints its initial +password once. The administrator chooses a new password at the first sign-in. +The command refuses to run on a server that already has an administrator. +On a new server, it first prints the steps that set up the server's database. + +```text +$ tiroir admin init +server database migrated to version 1 +… +member created: … +administrator: admin +initial password (shown once, to change at first sign-in): … +``` + +## `tiroir admin members` + +```text +usage: tiroir admin members [-h] {list,add,disable,reset-password} ... +``` + +Lists and changes the server's members. Without an action, it lists them. + +### `members list` + +```text +usage: tiroir admin members list [-h] +``` + +Prints one member per line: handle, display name, and `[admin]` or +`[disabled]` when they apply. + +```text +$ tiroir admin members list +admin Administrator [admin] +alice Alice +``` + +### `members add` + +```text +usage: tiroir admin members add [-h] --name NAME [--admin] handle +``` + +| Argument | Meaning | +|---|---| +| `handle` | The member's sign-in name: lowercase letters, digits, `-` and `_`. | +| `--name NAME` | The display name. Required. | +| `--admin` | Make the member an administrator. | + +Creates the member and prints an initial password once, to change at the first +sign-in. + +```text +$ tiroir admin members add alice --name Alice +member created: … +member: alice +initial password (shown once, to change at first sign-in): … +``` + +### `members disable` + +```text +usage: tiroir admin members disable [-h] handle +``` + +Disables the member and removes their access to every store: they can no +longer sign in. The handle stays reserved. + +```text +$ tiroir admin members disable alice +member disabled: … +alice: disabled +``` + +### `members reset-password` + +```text +usage: tiroir admin members reset-password [-h] handle +``` + +Gives the member a new password, printed once, to change at their next +sign-in. + +```text +$ tiroir admin members reset-password alice +member password reset: … +alice: new password (shown once, to change at next sign-in) +… +``` + +## `tiroir admin stores` + +```text +usage: tiroir admin stores [-h] {list,add} ... +``` + +Lists and creates stores. Without an action, it lists them. + +### `stores list` + +```text +usage: tiroir admin stores list [-h] +``` + +Prints one store per line: name, audience, language, and the number of members +with access to it. + +```text +$ tiroir admin stores list +alice-memory personal fr 1 members +team-memory team en 1 members +``` + +### `stores add` + +```text +usage: tiroir admin stores add [-h] --audience {personal,team} + --language LANGUAGE [--owner OWNER] + [--preset PRESET] + name +``` + +| Argument | Meaning | +|---|---| +| `name` | The store's name, a lowercase slug such as `alice-memory`. | +| `--audience {personal,team}` | `personal`: one member's memory. `team`: shared by the members you grant. Required. | +| `--language LANGUAGE` | The language of the store's content, such as `fr` or `en`. Required. | +| `--owner OWNER` | The handle of the member who owns the store. They get write access, and the store becomes their default. Required for a `personal` store. | +| `--preset PRESET` | The preset the store starts from. In 0.1.0, every store starts from a preset, and the only one is `accounting-firm`, filled with invented client drawers. | + +Creates the store and indexes it. + +```text +$ tiroir admin stores add alice-memory --audience personal --language fr \ + --owner alice --preset accounting-firm +store: alice-memory +``` + +## `tiroir admin grant` + +```text +usage: tiroir admin grant [-h] handle store {read,write} +``` + +| Argument | Meaning | +|---|---| +| `handle` | The member. | +| `store` | The store's name. | +| `{read,write}` | The access to give. Granting again replaces the previous access. | + +A personal store has its owner only: granting it to another member is refused. + +```text +$ tiroir admin grant bob team-memory read +bob: read access to team-memory +``` + +## `tiroir admin revoke` + +```text +usage: tiroir admin revoke [-h] handle store +``` + +Removes the member's access to the store. Revoking an access the member does +not have is not an error. + +```text +$ tiroir admin revoke bob team-memory +bob: no access to team-memory +``` + +## `tiroir admin index` + +```text +usage: tiroir admin index [-h] [--store STORE] [--rebuild] +``` + +| Option | Meaning | +|---|---| +| `--store STORE` | Index this store only. Without it, every store. | +| `--rebuild` | Rebuild the index from scratch instead of bringing it up to date. | + +Indexes are brought up to date when a store is created and after each write, +so this is rarely needed. It prints, per store, the files it indexed (none +when the index was already up to date) and the chunks the index holds. + +```text +$ tiroir admin index --store alice-memory +alice-memory: 0 files indexed, 399 chunks +$ tiroir admin index --rebuild +alice-memory: 247 files indexed, 399 chunks +team-memory: 247 files indexed, 399 chunks +``` + +## `tiroir admin serve` + +```text +usage: tiroir admin serve [-h] +``` + +Runs the server on port 8080. It is the image's entry point, which Compose +starts for you; you do not run it by hand. + +## Exit codes + +| Code | Meaning | +|---|---| +| `0` | Success. | +| `1` | The operation failed, for example an unknown member or a store name already used. The reason is on stderr. | +| `2` | Usage error: an unknown command or option, or a missing argument. The usage is on stderr. | +| `3` | Refused by an access rule, for example granting a personal store to a second member. | +| `5` | The store is busy; try again. | diff --git a/mise.toml b/mise.toml index 08286c7..89adc7b 100644 --- a/mise.toml +++ b/mise.toml @@ -46,6 +46,16 @@ run = "uv run pip-licenses --partial-match --fail-on=GPL --ignore-packages pygit description = "Run every check" depends = ["lint", "typecheck", "test"] +[tasks."docs:build"] +description = "Build the documentation site into site/, failing on any warning" +depends = ["sync"] +run = "uv run zensical build --clean --strict" + +[tasks."docs:serve"] +description = "Serve the documentation site locally, rebuilt on each change" +depends = ["sync"] +run = "uv run zensical serve" + [tasks.serve] description = "Run the server" depends = ["sync"] diff --git a/pyproject.toml b/pyproject.toml index 8c125d2..2308ed5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dev = [ "import-linter", "pip-audit", "pip-licenses", + "zensical==0.0.73", ] [build-system] diff --git a/uv.lock b/uv.lock index 083e54d..8035549 100644 --- a/uv.lock +++ b/uv.lock @@ -412,6 +412,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/f0/b2cb999244f2f4194df63ecff6939228178fa63a33be809c412684ca8db7/cyclonedx_python_lib-11.12.0-py3-none-any.whl", hash = "sha256:0e807521a921a5c3cb8ce1153f8a61d29eedfe76a46aac2796b7c6b573391a54", size = 529453, upload-time = "2026-08-13T07:52:16.836Z" }, ] +[[package]] +name = "deepmerge" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/e9/5e99e56e03f9461858e686fdaf82e2516671bc1bd46f8b7ad7b9a8375f6f/deepmerge-4.0.0.tar.gz", hash = "sha256:948565f2bc9cdb41259a4a23d26638e5840c9e084a343ab279ffd3edcfdf74d3", size = 22792, upload-time = "2026-10-25T19:25:28.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/89/362146b2ec7d0d503f824ac8496c88f0408ee0d39203b02cc10feac08146/deepmerge-4.0.0-py3-none-any.whl", hash = "sha256:a36dd5810fc3b90bd3e0b681158221361d6a31a6109411b27b211b371fed6d5b", size = 15429, upload-time = "2026-10-25T19:25:28.012Z" }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -736,6 +745,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "markdown" +version = "3.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/32/0b4d0297064244f04af4331b65de16237cc3cce07474171ee14222a28981/markdown-3.11.2.tar.gz", hash = "sha256:87bf5d90f2d2882e2c02fcda182c2800456b985bff38913867c49706481514b0", size = 491445, upload-time = "2026-10-19T19:41:42.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/66/bcab3b677b71ad286b4e607e3427499537c76e13ad2e22584ba7a7d9882c/markdown-3.11.2-py3-none-any.whl", hash = "sha256:f80390747a2a0ff2ba54da074ce4f862f72a6685f608cd2637b646a6d83993f5", size = 114266, upload-time = "2026-10-19T19:41:41.244Z" }, +] + [[package]] name = "markdown-it-py" version = "4.2.0" @@ -989,6 +1007,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, ] +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + [[package]] name = "pip" version = "26.2.2" @@ -1279,6 +1306,19 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pymdown-extensions" +version = "12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/fe/ac50ee24a80a1b344e643f6fb626063cc7041b978cd1dd9a7f9963ae1cd1/pymdown_extensions-12.3.tar.gz", hash = "sha256:a7f2a4e0bc60c6e6babfa7922cfb3b01cd640eb00fb208f3c582e12bae00ea67", size = 870532, upload-time = "2026-10-14T22:40:43.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/d0/758afe732c2da502c051b4d03ff88daa3c0fc1a1e671e6c5a6318177bab4/pymdown_extensions-12.3-py3-none-any.whl", hash = "sha256:345493a8e3497e24382902903a1c43a28e34a52f2b7571a1c9641429b820ca74", size = 277597, upload-time = "2026-10-14T22:40:41.543Z" }, +] + [[package]] name = "pyparsing" version = "3.3.3" @@ -1383,6 +1423,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + [[package]] name = "referencing" version = "0.37.0" @@ -1600,6 +1676,7 @@ dev = [ { name = "pyrefly" }, { name = "pytest" }, { name = "ruff" }, + { name = "zensical" }, ] [package.metadata] @@ -1626,6 +1703,7 @@ dev = [ { name = "pyrefly" }, { name = "pytest" }, { name = "ruff" }, + { name = "zensical", specifier = "==0.0.73" }, ] [[package]] @@ -1787,3 +1865,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b66 wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, ] + +[[package]] +name = "zensical" +version = "0.0.73" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "pathspec" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/49/1dada916f2537c065da395e0188fdc83d66a983af9926cf188e7150ce0cc/zensical-0.0.73.tar.gz", hash = "sha256:ddeca0695e1015a4fbd09b9a9e50e77a2bb43753bf7479dfb23255f35fd14450", size = 4350383, upload-time = "2026-10-26T13:04:05.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/94/bc67421deb1e2c06776545d2450ec8142b132f587240ec87d33d3f196fe5/zensical-0.0.73-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7bf70ebdb80628e17b5597d5da5669514df37b9e983b2c40e814b65c0f1bd9e1", size = 18278864, upload-time = "2026-10-26T12:05:47.987Z" }, + { url = "https://files.pythonhosted.org/packages/e2/bf/6fda434e479e86fbc9c32d14a1641ac7a75d4ed07c840420e13049bd2f41/zensical-0.0.73-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:8411c5c16cefd04e139dedbdc38ae59cf503e4cc83b03613a790bbab64e2aace", size = 17799234, upload-time = "2026-10-26T12:05:51.043Z" }, + { url = "https://files.pythonhosted.org/packages/24/ec/82c3964ae15db6659150f01217ac858355e6cd10f0ac65f8d9be9fa609d3/zensical-0.0.73-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e27878c515ef1695b3587b762c2738c93dee1a0412c4c9b240583a968535e45", size = 18300892, upload-time = "2026-10-26T12:05:53.953Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/0a6ac4434cbaa7764ecf7d41a54b8ea81ae08c24fd2f5414cb61a60b0598/zensical-0.0.73-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b45462e8925f8eb1d45aeed09dea1450c57d2533bfc28214af4a4a9e910e9e", size = 18798355, upload-time = "2026-10-26T12:05:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/6d9b7a36b6f950e124b1711de16588da1d4e436097c94afede8d8de4d8d9/zensical-0.0.73-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5c79302dec347e1d2c7b3e0961ac56c97134f59c6f5cff786b81accf50b3bbb2", size = 18476606, upload-time = "2026-10-26T12:06:01.184Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/1e9b02225ceb6905c50d320e8e9edd3d541de4243b785d6c4313e605ec36/zensical-0.0.73-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:02a20d9cce5a5b976671d02550a8e65fcea49230a12978bf3c13b497f542ea16", size = 19057264, upload-time = "2026-10-26T12:06:03.485Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/6f894a0efe07863f0b16afc7c0aa524fd33efb3565c338b89a8c3a9b0c04/zensical-0.0.73-cp311-abi3-win_amd64.whl", hash = "sha256:d8f7b9e03a6280f67f55afd1a9a17937778d77ba7490124b11393bb79220a60d", size = 19005486, upload-time = "2026-10-26T13:04:00.768Z" }, + { url = "https://files.pythonhosted.org/packages/3c/18/1b25f5d450fbc3145d595f9d3a414ae993da6db002b476f68eb8f9c17bd0/zensical-0.0.73-cp311-abi3-win_arm64.whl", hash = "sha256:a68b0d23b9de360a1f1867c56c786d77947616eca42195c4cc1f18a1e1686bbf", size = 18408634, upload-time = "2026-10-26T13:04:03.376Z" }, +] diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 0000000..bf13ec1 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,32 @@ +# The user documentation site, built by `mise run docs:build` and published to +# GitHub Pages from `develop` by .github/workflows/docs.yml. +[project] +site_name = "tiroir" +site_description = "Configurable, scoped memory for AI agents, served to your team from your own server." +site_url = "https://ancelin-code.github.io/tiroir/" +repo_url = "https://github.com/ancelin-code/tiroir" +edit_uri = "edit/develop/docs" +copyright = "Apache-2.0" +strict = true + +# The site is the user documentation only: the brief, the research notes and +# the milestone notes stay in the repository, out of the site. +exclude_docs = """ +brief.md +research/ +milestones/ +""" + +nav = [ + { "Home" = "index.md" }, + { "Install (VPS)" = "install/vps.md" }, + { "Connect (Claude Desktop)" = "connect/claude-desktop.md" }, + { "Reference (CLI)" = "reference/cli.md" }, +] + +[project.plugins.llmstxt] +markdown_description = "tiroir is a self-hosted memory server for teams of AI agents: install it on a VPS, connect Claude Desktop, and administer it from the command line." + +[project.plugins.llmstxt.sections] +"Get started" = ["index.md", "install/vps.md", "connect/claude-desktop.md"] +Reference = ["reference/cli.md"]