diff --git a/.env.example b/.env.example index f62c5394..897c1f55 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,7 @@ +# An install created before the rename from Scribe keeps its database by +# setting these, and PGDATA_VOLUME, to the names it was created with. POSTGRES_USER=fabled POSTGRES_PASSWORD=fabled -POSTGRES_DB=scribe +POSTGRES_DB=grimoire +# PGDATA_VOLUME=grimoire_pgdata SECRET_KEY=dev-secret-change-me diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 9609e3ff..697a5621 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -55,7 +55,7 @@ permissions: env: REGISTRY: git.fabledsword.com - IMAGE: git.fabledsword.com/bvandeusen/fabledscribe + IMAGE: git.fabledsword.com/bvandeusen/fabledgrimoire jobs: typecheck: @@ -193,11 +193,11 @@ jobs: # Same image as the shipped compose files: Postgres 17 with pgvector. image: pgvector/pgvector:pg17 env: - POSTGRES_USER: scribe + POSTGRES_USER: grimoire POSTGRES_PASSWORD: ci_integration - POSTGRES_DB: scribe_test + POSTGRES_DB: grimoire_test options: >- - --health-cmd "pg_isready -U scribe" + --health-cmd "pg_isready -U grimoire" --health-interval 10s --health-timeout 5s --health-retries 10 @@ -235,7 +235,7 @@ jobs: test "$(printf '%s\n' "$PG" | grep -c .)" -eq 1 PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$PG") test -n "$PG_IP" - export DATABASE_URL="postgresql+asyncpg://scribe:ci_integration@${PG_IP}:5432/scribe_test" + export DATABASE_URL="postgresql+asyncpg://grimoire:ci_integration@${PG_IP}:5432/grimoire_test" # Wait for Postgres. The run: shell is dash (no bash /dev/tcp), so use Python. /opt/venv/bin/python - "$PG_IP" <<'PY' import socket, sys, time diff --git a/Dockerfile b/Dockerfile index 248d0b9d..d53ffedd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \ # alembic and hypercorn in CMD have to be found there. ENV PATH="/app/.venv/bin:$PATH" -COPY --from=build-frontend /build/dist/ src/scribe/static/ +COPY --from=build-frontend /build/dist/ src/grimoire/static/ COPY alembic.ini . COPY alembic/ alembic/ @@ -57,4 +57,4 @@ ENV APP_CHANNEL=$BUILD_CHANNEL ENV APP_COMMIT=$BUILD_COMMIT EXPOSE 5000 -CMD ["sh", "-c", "alembic upgrade head && hypercorn 'scribe.app:create_app()' --bind 0.0.0.0:5000 --keep-alive 600"] +CMD ["sh", "-c", "alembic upgrade head && hypercorn 'grimoire.app:create_app()' --bind 0.0.0.0:5000 --keep-alive 600"] diff --git a/alembic/env.py b/alembic/env.py index 0ba83fd2..e899d3df 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -4,9 +4,9 @@ from logging.config import fileConfig from alembic import context from sqlalchemy.ext.asyncio import create_async_engine -from scribe.config import Config -from scribe.models import Base -from scribe.services.db_ready import wait_for_database +from grimoire.config import Config +from grimoire.models import Base +from grimoire.services.db_ready import wait_for_database config = context.config if config.config_file_name is not None: diff --git a/alembic/versions/0122_seal_forge_tokens.py b/alembic/versions/0122_seal_forge_tokens.py index 86c8bacd..b3821a11 100644 --- a/alembic/versions/0122_seal_forge_tokens.py +++ b/alembic/versions/0122_seal_forge_tokens.py @@ -43,7 +43,7 @@ _LABEL_V1 = b"scribe.secrets.v1\x00" def _fernet(): from cryptography.fernet import Fernet - from scribe.config import Config + from grimoire.config import Config digest = hashlib.sha256(_LABEL_V1 + Config.SECRET_KEY.encode()).digest() return Fernet(base64.urlsafe_b64encode(digest)) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index c821b44e..1d19d84c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,14 +1,14 @@ services: app: - image: git.fabledsword.com/bvandeusen/fabledscribe:latest + image: git.fabledsword.com/bvandeusen/fabledgrimoire:latest environment: - DATABASE_URL: "postgresql+asyncpg://scribe:${DB_PASSWORD}@db:5432/scribe" + DATABASE_URL: "postgresql+asyncpg://${POSTGRES_USER:-grimoire}:${DB_PASSWORD}@db:5432/${POSTGRES_DB:-grimoire}" SECRET_KEY: "${SECRET_KEY}" LOG_LEVEL: "${LOG_LEVEL:-INFO}" TRUST_PROXY_HEADERS: "true" SECURE_COOKIES: "true" networks: - - scribe_backend + - backend db: # Postgres 17 with the vector extension Grimoire needs. @@ -17,15 +17,16 @@ services: volumes: - pgdata:/var/lib/postgresql/data environment: - POSTGRES_USER: scribe + POSTGRES_USER: ${POSTGRES_USER:-grimoire} POSTGRES_PASSWORD: "${DB_PASSWORD}" - POSTGRES_DB: scribe + POSTGRES_DB: ${POSTGRES_DB:-grimoire} networks: - - scribe_backend + - backend volumes: pgdata: + name: ${PGDATA_VOLUME:-grimoire_pgdata} networks: - scribe_backend: + backend: driver: overlay diff --git a/docker-compose.quickstart.yml b/docker-compose.quickstart.yml index 62133c39..90af1f63 100644 --- a/docker-compose.quickstart.yml +++ b/docker-compose.quickstart.yml @@ -5,11 +5,11 @@ services: app: - image: git.fabledsword.com/bvandeusen/fabledscribe:latest + image: git.fabledsword.com/bvandeusen/fabledgrimoire:latest ports: - "5000:5000" environment: - DATABASE_URL: "postgresql+asyncpg://scribe:scribe@db:5432/scribe" + DATABASE_URL: "postgresql+asyncpg://${POSTGRES_USER:-grimoire}:${POSTGRES_PASSWORD:-grimoire}@db:5432/${POSTGRES_DB:-grimoire}" # Set SECRET_KEY in the environment or a .env file beside this one. SECRET_KEY: "${SECRET_KEY:-change-me-in-production}" LOG_LEVEL: "${LOG_LEVEL:-INFO}" @@ -26,9 +26,9 @@ services: volumes: - pgdata:/var/lib/postgresql/data environment: - POSTGRES_USER: scribe - POSTGRES_PASSWORD: scribe - POSTGRES_DB: scribe + POSTGRES_USER: ${POSTGRES_USER:-grimoire} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-grimoire} + POSTGRES_DB: ${POSTGRES_DB:-grimoire} restart: unless-stopped volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 3aed715a..2a420706 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: # Or a bind mount, for direct host access to the app's data: # - ./data:/data environment: - DATABASE_URL: "postgresql+asyncpg://${POSTGRES_USER:-scribe}:${POSTGRES_PASSWORD:-scribe}@db:5432/${POSTGRES_DB:-scribe}" + DATABASE_URL: "postgresql+asyncpg://${POSTGRES_USER:-grimoire}:${POSTGRES_PASSWORD:-grimoire}@db:5432/${POSTGRES_DB:-grimoire}" SECRET_KEY: "${SECRET_KEY:-dev-secret-change-me}" # Optional: a SearXNG instance to list as the web-search backend in Integrations. # SEARXNG_URL: "http://searxng:8080" @@ -24,9 +24,9 @@ services: volumes: - pgdata:/var/lib/postgresql/data environment: - POSTGRES_USER: ${POSTGRES_USER:-scribe} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-scribe} - POSTGRES_DB: ${POSTGRES_DB:-scribe} + POSTGRES_USER: ${POSTGRES_USER:-grimoire} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-grimoire} + POSTGRES_DB: ${POSTGRES_DB:-grimoire} volumes: pgdata: diff --git a/docs/api-keys-and-mcp.md b/docs/api-keys-and-mcp.md index 77815ed6..775b3a7e 100644 --- a/docs/api-keys-and-mcp.md +++ b/docs/api-keys-and-mcp.md @@ -35,7 +35,7 @@ Click **Revoke** next to the key in the keys table and confirm. Revoked keys are Grimoire exposes itself as a set of MCP tools that Claude (and other MCP clients) can use to read and write your notes, tasks, projects, rulebooks, and more. The server is **built into the app** — it is mounted as a streamable-HTTP endpoint -at **`/mcp`** on the running Grimoire instance (`src/scribe/mcp/server.py`). There +at **`/mcp`** on the running Grimoire instance (`src/grimoire/mcp/server.py`). There is nothing to install: no wheel, no separate package, no CLI. You connect a client straight to the URL with a Bearer token. @@ -90,7 +90,7 @@ specific project at a dev instance or an admin key. ### Available Tools There are well over a hundred tools and they change with the app, so the -registration in **`src/scribe/mcp/tools/`** is the reference. By family: +registration in **`src/grimoire/mcp/tools/`** is the reference. By family: | Family | Examples | Purpose | |--------|----------|---------| @@ -111,4 +111,4 @@ registration in **`src/scribe/mcp/tools/`** is the reference. By family: Server-level usage guidance — when to reach for each entity, the recall-before-acting reflex, and the rulebook conventions — is delivered to the client automatically via the MCP server's `instructions` block (defined in -`src/scribe/mcp/server.py`). +`src/grimoire/mcp/server.py`). diff --git a/docs/api-reference.md b/docs/api-reference.md index c815b8aa..c953cba0 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -294,7 +294,7 @@ These serve the plugin's hooks. ## Grimoire MCP Claude clients connect to the built-in MCP server at `POST /mcp` (streamable HTTP, -Bearer auth with an `fmcp_` key), served by `src/scribe/mcp/`. It is not a REST +Bearer auth with an `fmcp_` key), served by `src/grimoire/mcp/`. It is not a REST surface — it exposes the same data as typed tools (`create_note`, `create_task`, `start_planning`, `search`, `enter_project`, …) with server-level usage guidance delivered in the MCP `instructions` block. See diff --git a/docs/architecture.md b/docs/architecture.md index 886a5098..735821cb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -24,10 +24,10 @@ Grimoire is one app container and one PostgreSQL container. There is no language ``` - **The web UI** is a Vue single-page app, built into the image and served by Quart. Every unknown path that isn't under `/api/` falls back to `index.html`; `/api/*` always answers JSON. -- **The REST API** (`src/scribe/routes/`) backs the UI, one blueprint per resource. -- **The MCP server** (`src/scribe/mcp/`) is how Claude reads and writes records. Each module in `mcp/tools/` registers its tools, and the server's instructions and each tool's docstring are the contract an agent reads. Requests authenticate with an API key as a Bearer token. +- **The REST API** (`src/grimoire/routes/`) backs the UI, one blueprint per resource. +- **The MCP server** (`src/grimoire/mcp/`) is how Claude reads and writes records. Each module in `mcp/tools/` registers its tools, and the server's instructions and each tool's docstring are the contract an agent reads. Requests authenticate with an API key as a Bearer token. - **The plugin** (`plugin/`) runs in the user's Claude Code. Its hooks call `/api/plugin/*` to put the right context in front of Claude at the right moment: the project at session start, matching rules and prior art before a prompt or a tool call, the rules mounted on a moment of work, and a check on the reply that ends a turn. Its skills describe the working practices. See [API Keys & MCP](api-keys-and-mcp.md) to install it. -- **Services** (`src/scribe/services/`) hold the logic both doors share. Routes and MCP tools stay thin: parse the request, check access, call a service, return a plain dict. +- **Services** (`src/grimoire/services/`) hold the logic both doors share. Routes and MCP tools stay thin: parse the request, check access, call a service, return a plain dict. ## Data model @@ -80,14 +80,14 @@ Credentials Grimoire stores for outside services (forge tokens, the backup desti ``` ├── alembic/versions/ database migrations -├── src/scribe/ +├── src/grimoire/ │ ├── app.py app factory, blueprints, startup and shutdown │ ├── config.py environment configuration │ ├── models/ SQLAlchemy models │ ├── routes/ REST blueprints │ ├── mcp/ MCP server and its tool modules │ ├── services/ shared logic -│ └── restore.py `python -m scribe.restore` for a fresh instance +│ └── restore.py `python -m grimoire.restore` for a fresh instance ├── frontend/src/ views, components, stores, composables, api client ├── plugin/ Claude Code hooks, skills and commands ├── scripts/ CI checks and the plugin version minter diff --git a/docs/backups.md b/docs/backups.md index 9a4e00d2..df93be32 100644 --- a/docs/backups.md +++ b/docs/backups.md @@ -11,9 +11,12 @@ Set it up under **Settings → Data → Scheduled backups** (admins only). One file per run, named for the moment it was taken in UTC: ``` -scribe-20261015T020000Z.tar +grimoire-20261015T020000Z.tar ``` +Backups written while the app was called Scribe are named `scribe-…tar`. +They are counted with the rest and restore the same way. + It is a plain, **uncompressed** tar: - `manifest.json` comes first. It holds the backup format version, the Grimoire @@ -65,12 +68,12 @@ A path **inside the Grimoire container**, normally a share mounted there: ```yaml services: - scribe: + grimoire: volumes: - - /nfs/backup/scribe:/backups + - /nfs/backup/grimoire:/backups ``` -Grimoire writes each file under a hidden name (`.scribe-….tar.part`) and renames +Grimoire writes each file under a hidden name (`.grimoire-….tar.part`) and renames it once it is complete. A program watching the folder never sees half a backup. @@ -115,7 +118,7 @@ page asks for the secret again. [Fabled Archives](https://git.fabledsword.com/bvandeusen/fabledarchives) keeps service backups as "backup targets". Either destination works with it. -1. On Archives' **Backups** page, make a target for Grimoire, named `scribe` +1. On Archives' **Backups** page, make a target for Grimoire, named `grimoire` for example. 2. Choose how Grimoire's files arrive: - **S3**: turn on the S3 way in for the target and make an **append-only** @@ -124,8 +127,8 @@ keeps service backups as "backup targets". Either destination works with it. - bucket: the target's name; - access key and secret key: the key you just made. - **A folder**: mount the share Archives watches into Grimoire as - `/backups/scribe`, and point the target at that folder. In Grimoire, choose - **A folder** with `/backups/scribe`. + `/backups/grimoire`, and point the target at that folder. In Grimoire, choose + **A folder** with `/backups/grimoire`. 3. Set the target's **Retention** to **Archives**. Archives then keeps the newest backups and thins the rest by day, week and month. Grimoire only adds. 4. Give the target an expected cadence of **daily**, so a missed backup turns @@ -141,7 +144,7 @@ Start Grimoire on an empty database, copy the backup file into the container, and run: ```sh -docker exec -it scribe python -m scribe.restore /backups/scribe-20261015T020000Z.tar +docker exec -it grimoire python -m grimoire.restore /backups/grimoire-20261015T020000Z.tar ``` - The command refuses a database that already has an account. A restore diff --git a/docs/configuration.md b/docs/configuration.md index 56461714..ac7e37ad 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -2,7 +2,7 @@ Grimoire reads its configuration from environment variables. Most settings people change day to day (registration, SMTP, retrieval thresholds, backups, forge connections) live in **Settings** in the web UI instead. -Any variable marked *secret* can also be read from a file: set `_FILE` to the path, for example a Docker secret at `/run/secrets/scribe_key`. +Any variable marked *secret* can also be read from a file: set `_FILE` to the path, for example a Docker secret at `/run/secrets/grimoire_key`. ## Environment variables @@ -10,7 +10,7 @@ Any variable marked *secret* can also be read from a file: set `_FILE` to | Variable | Default | Description | |----------|---------|-------------| -| `DATABASE_URL` *(secret)* | `postgresql+asyncpg://scribe:scribe@localhost:5432/scribe` | PostgreSQL connection string, using the `asyncpg` driver | +| `DATABASE_URL` *(secret)* | `postgresql+asyncpg://grimoire:grimoire@localhost:5432/grimoire` | PostgreSQL connection string, using the `asyncpg` driver | | `SECRET_KEY` *(secret)* | `dev-secret-change-me` | Signs sessions and seals stored credentials. Set it before first use and keep it (see below) | | `BASE_URL` | `http://localhost:5000` | Public URL, used in email links and OIDC redirects | | `SECURE_COOKIES` | `false` | `true` when Grimoire is served over HTTPS | @@ -79,9 +79,11 @@ docker compose up --build [`docker-compose.prod.yml`](../docker-compose.prod.yml) is a stack file: the app and database on a private overlay network, with `SECURE_COOKIES` and `TRUST_PROXY_HEADERS` on. Provide `DB_PASSWORD` and `SECRET_KEY` in the environment: ```bash -DB_PASSWORD=... SECRET_KEY=... docker stack deploy -c docker-compose.prod.yml scribe +DB_PASSWORD=... SECRET_KEY=... docker stack deploy -c docker-compose.prod.yml grimoire ``` +The database user, database and volume default to `grimoire`. An install created under the name Scribe keeps its data by setting `POSTGRES_USER`, `POSTGRES_DB` and `PGDATA_VOLUME` to the names it was created with. + The shipped files leave out healthchecks and restart policies on purpose. The app waits for its database on startup, and the orchestrator's defaults restart it if it ever exits. ### Volumes diff --git a/docs/development.md b/docs/development.md index 1a925a8f..0e02dc81 100644 --- a/docs/development.md +++ b/docs/development.md @@ -65,7 +65,7 @@ Work happens on `dev`. A release merges `dev` into `main` with a plain merge com ### Backend -- Services open their own session: `async with async_session() as session:` (from `scribe.models`). +- Services open their own session: `async with async_session() as session:` (from `grimoire.models`). - Routes and MCP tools stay thin; the logic lives in `services/`, shared by both. - Access checks go through `services/access.py`, never inline ownership checks. - Every wait on something outside the process has a deadline. diff --git a/docs/features.md b/docs/features.md index 1180b8dd..e7599746 100644 --- a/docs/features.md +++ b/docs/features.md @@ -86,7 +86,7 @@ Share a project, note or task with users or groups, as viewer, editor or admin. - **Personal export**: your notes and tasks as a Markdown ZIP (with YAML frontmatter) or JSON. - **Admin backup and restore**, with ids remapped so a backup restores onto another instance. -- **Scheduled backups**: a daily, dated, uncompressed tar to a folder or an S3-compatible bucket, laid out so a deduplicating tool such as restic stores only what changed. `python -m scribe.restore` brings a fresh instance back from one. See [Backups](backups.md). +- **Scheduled backups**: a daily, dated, uncompressed tar to a folder or an S3-compatible bucket, laid out so a deduplicating tool such as restic stores only what changed. `python -m grimoire.restore` brings a fresh instance back from one. See [Backups](backups.md). ## Sign-in diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc64e610..6e270dc0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "scribe-frontend", + "name": "grimoire-frontend", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "scribe-frontend", + "name": "grimoire-frontend", "version": "0.1.0", "dependencies": { "@fullcalendar/core": "^6.1.20", diff --git a/frontend/package.json b/frontend/package.json index 927b7801..74027f36 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "scribe-frontend", + "name": "grimoire-frontend", "private": true, "version": "0.1.0", "type": "module", diff --git a/frontend/src/api/version.ts b/frontend/src/api/version.ts index 79e89f00..fe5cead8 100644 --- a/frontend/src/api/version.ts +++ b/frontend/src/api/version.ts @@ -2,7 +2,7 @@ import { apiGet } from "./client"; /** * What `/api/version` answers — the client's half of `build_version_payload` - * (`src/scribe/routes/api.py`), which is where the reasoning for the shape is + * (`src/grimoire/routes/api.py`), which is where the reasoning for the shape is * written down. * * EVERY FIELD BUT `version` IS OPTIONAL, and an absent one means "this build diff --git a/frontend/src/components/BackupScheduleSettings.vue b/frontend/src/components/BackupScheduleSettings.vue index f93870c2..4386ebdb 100644 --- a/frontend/src/components/BackupScheduleSettings.vue +++ b/frontend/src/components/BackupScheduleSettings.vue @@ -187,7 +187,7 @@ async function backUpNow() {

- Each run adds one dated file, scribe-<date>.tar, holding every table + Each run adds one dated file, grimoire-<date>.tar, holding every table as plain lines. It is not compressed, so a deduplicating tool such as restic stores only what changed since the day before. Grimoire never deletes old backups — the destination keeps and thins the history. Sharing, API keys and forge connections are not included. @@ -237,7 +237,7 @@ async function backUpNow() {