Metadata-Version: 2.4
Name: atlas-mind
Version: 0.3.9
Summary: Self-hostable, AI-native knowledge base engine — an external brain you share with your AI.
Author: The Atlas Mind authors
License: AGPL-3.0-or-later
Project-URL: Homepage, https://github.com/Anakior/atlas-mind
Project-URL: Repository, https://github.com/Anakior/atlas-mind
Project-URL: Issues, https://github.com/Anakior/atlas-mind/issues
Keywords: knowledge-base,wiki,markdown,self-hosted,mcp,ai,notes
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: End Users/Desktop
Classifier: Topic :: Documentation
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Provides-Extra: bcrypt
Requires-Dist: bcrypt>=4.1; extra == "bcrypt"
Dynamic: license-file

# Atlas Mind

[![Live demo](https://img.shields.io/badge/live-demo-635bff.svg)](https://atlas-mind.anakior.app/) [![Licence: AGPL v3](https://img.shields.io/badge/licence-AGPL--3.0-blue.svg)](LICENSE) ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg) ![Dependencies: standard library](https://img.shields.io/badge/deps-standard%20library-green.svg) ![Database: none](https://img.shields.io/badge/database-none-green.svg)

> **It's your mind, not someone else's.**

Notion, and every hosted notes app, ask you to pour your thinking into
*their* structure, on *their* servers, in *their* format. You rent the space;
they hold the keys. Atlas Mind inverts that: your knowledge is **plain files in
your own git repository**, served by an **engine you can read line by line** and
throw away without losing a single note. Your AI comes to *your* library through
an open protocol — it never pulls your brain into its own.

Own the data. Own the engine. Own the mind.

<p align="center">
  <img alt="Atlas Mind viewer" width="900" src="https://github.com/user-attachments/assets/726ab344-b024-425f-a5ee-f56d97a886c7" />
</p>

> 🔗 **[Try the live demo →](https://atlas-mind.anakior.app/demo/)** — this is the offline build, some features are missing. ([What is Atlas Mind?](https://atlas-mind.anakior.app/) — the landing page.)

**Atlas Mind** is a self-hostable wiki / knowledge base engine — and an external
brain you share with your AI. It serves a single-page viewer from a folder of
documents (a *mind*), and keeps the **engine** (the code) cleanly decoupled from
your **content** (your notes, in their own git repository).

It is built on three ideas:

- **Multi-format, not just Markdown.** Markdown is first-class (rendered, linked,
  searched), but Atlas Mind also previews **standalone HTML** documents (decks,
  dashboards), **PDFs**, and **Word `.docx`** files inline — converted to readable
  HTML in your browser, nothing uploaded anywhere. Your knowledge lives in one
  place whatever its shape.
- **AI-native.** Atlas Mind is meant to be the memory your AI assistant reads and
  enriches: it exposes an **MCP** endpoint (seven tools) and `atlas init` scaffolds
  the conventions (`AGENTS.md`, `agents/`, `ai-sessions/`) so an assistant knows how
  to use your mind. See [Use it with your AI](#use-it-with-your-ai-mcp).
- **Lightweight & self-contained.** A single Python HTTP server plus a build step,
  written entirely on the standard library — **no database required**, accounts
  and share links live in plain JSON files on disk. Frontend libraries and fonts
  are vendored, so a running instance makes no third-party network calls — your
  mind never leaves your disk, and nothing you write trains anyone's model.

## Who it is for

- Anyone who wants a personal or small-team knowledge base they fully own.
- People who already keep notes as Markdown in git and want a viewer, search,
  linking and an AI integration on top — without a database or a hosted service.
- Self-hosters who want one process behind a reverse proxy, or a small container
  on a PaaS such as Fly.io.

It is not a multi-tenant SaaS, a real-time collaborative editor, or a plugin
marketplace. It is a focused engine for one mind per instance.

## Contents

[How it works](#how-it-works) · [Features](#features) · [Requirements](#requirements) ·
[Install](#install) · [Quick start](#quick-start) ·
[Use it with your AI](#use-it-with-your-ai-mcp) ·
[Atlas nodes](#hive-atlas-nodes) · [Configuration](#configuration) ·
[Updating](#updating) · [Deployment](#deployment) ·
[Building from source](#building-from-source) · [Extensions](#extensions) ·
[Security model](#security-model) · [Licence](#licence)

## How it works

Atlas Mind is two things kept deliberately apart:

- The **engine** — this repository: the Python server, the build step, and the
  single-page viewer.
- A **mind** — your content, in its own git repository: a `content/` folder of
  documents plus a small `atlas.toml`. Accounts, tokens and share links live in a
  local `.atlas/` directory (plain JSON, gitignored), so a mind carries its own
  state and **no database is involved**.

You point the engine at a mind and it serves a fast single-page app: a build step
turns `content/` into a search/backlink index and a viewer shell, the server
serves it, and edits made in the viewer are written back to the files (and, in
cloud mode, committed and pushed). Upgrading the engine never touches your
content; syncing content never touches the engine.

That separation is the whole point: if Atlas Mind disappeared tomorrow, your mind
would still open in any text editor — it is just a folder of files in your own git
repository. You are never holding someone else's format hostage.

## Features

Everything below ships in the box.

### Reading & multi-format viewer

- **Markdown** is first-class: rendered with GitHub-flavoured Markdown, syntax
  highlighting, a table of contents, reading time, and a per-document tag bar.
- **Standalone HTML** documents (slide decks, dashboards) are previewed inline in
  a sandboxed iframe — their own CSS/JS runs, isolated from the viewer.
- **PDF** is previewed inline in the browser's native viewer.
- **Word `.docx`** is converted to readable HTML **in the browser** (via a
  lazily-loaded library) and shown with the same prose styling as Markdown —
  read-only, and nothing ever leaves your machine. (`.pptx` and other binaries
  are offered as a direct download.)
- Every file type can be **downloaded** as its original from the toolbar.

### Navigation & discovery

- **Tree sidebar** over `content/`, collapsible, with rename / share-as-node
  actions on hover.
- **Full-text search** — server-side when online (transfers only the results),
  or MiniSearch (fuzzy, prefix) over embedded content when offline.
- **Wikilinks `[[...]]`, backlinks, and "same subject"** suggestions from shared
  tags, surfaced in a side panel; a wikilink/backlink index is built at build time.
- **Tags** derived from parent folders and YAML frontmatter, each with its own
  per-tag view.
- **Task rollup** — every `- [ ]` / `- [x]` across the whole mind gathered in one
  view, grouped by document, "show done" toggle, and a click that jumps straight
  to the task in its document (the per-document todo widget stays separate).
- **The Mind** — a force-directed "mind palace": every document and tag is a node
  on a navigable map (drag, pan, zoom), colour-grouped into regions by folder,
  with recently-edited nodes glowing and orphans dimmed. Subscribed remote nodes
  appear as their own distinct, teal regions (see
  [Atlas nodes](#hive-atlas-nodes)).
- **Command palette** (Ctrl-K), **pinned** and **recently-modified** documents,
  and a quick-capture button.

### Editing

- Create, edit, **rename**, move and **delete** documents straight from the
  viewer; moving a document **rewrites the wikilinks** that point at it.
- **Document templates** — new documents can start from a base skeleton (note,
  meeting, project, decision/ADR, literature note, how-to, contact, retro, spec);
  drop a `.md` in `templates/` to add or override your own.
- **Interactive task checkboxes** — tick a `- [ ]` item in a rendered document and
  the change is written straight back to the file, no full re-render flicker.
  (Live engine only — the offline build / demo can't write, so its checkboxes are
  read-only.)
- **Todos** — a small categorised CRUD widget backed by a Markdown file, with a
  count badge on the favicon.
- All edits in cloud mode are committed and pushed by the instance itself.

### History (git time-travel)

- Every document is **git-versioned**, so the viewer surfaces its **history**: a
  side panel lists each revision (date, author, message), shows a **clean diff**
  between versions, lets you **view a past version**, and **restore** one — the
  restore is a new commit on top, nothing is rewritten. Renames and moves are
  followed, so a document keeps its full, loadable history even after a `move_doc`.

### Notes (annotations)

- Attach a **note to a text selection** in any Markdown document; the highlight
  persists and re-anchors on later visits (orphaned notes are flagged if the text
  changed). A tree badge shows each document's note count, live.
- **Copy all notes** of a document as Markdown (quote + note) in one click — handy
  for sharing your annotations, including on a read-only remote node.

### Sharing

- **Public share links** — an HMAC-signed token that serves a single document
  with no login, with optional expiry and one-click revocation. Useful to hand a
  page to someone outside your instance.

### Hive Mind (Atlas nodes)

Share a folder or document with **another Atlas Mind instance**, read-only, kept
in sync. See the dedicated section: [Atlas nodes](#hive-atlas-nodes).

### Multi-user & permissions (cloud mode)

- **Cloud mode** turns on accounts and login. Two roles: **admin** (full access +
  the Settings panel: users, tokens, shares, nodes) and **viewer** (read-only).
- **Per-viewer document permissions** — an admin can hide chosen folders from a
  given viewer; hidden paths are filtered server-side everywhere (tree, search,
  direct URL, backlinks), never just visually masked.

### Security & authentication

Cloud mode brings cookie sessions, optional **TOTP 2FA** with single-use recovery
codes, per-account lockout with backoff and per-IP login rate limiting, and full
CSRF defence. See the [Security model](#security-model) for the honest details.

### AI integration

- An **MCP endpoint** exposing twelve tools so an assistant reads, writes and
  *navigates* your mind directly: `search_docs` (with an optional `tag` filter),
  `read_doc`, `list_tree`, `recent_docs`, `create_doc`, `edit_doc`, `move_doc`,
  `delete_doc` (soft-delete to `.trash/`, reversible), plus graph traversal —
  `get_links`, `get_backlinks`, `list_by_tag`, and `get_mind_topology` (a
  bird's-eye summary: hubs, orphans, density, top tags).
- A **REST API v1** (Bearer tokens, create-only writes) with a published
  **OpenAPI 3.1** spec.
- `atlas init` scaffolds the conventions (`AGENTS.md`, `agents/`, `ai-sessions/`). See
  [Use it with your AI](#use-it-with-your-ai-mcp).

### Offline, PWA & sync

- **Offline build** — a single self-contained `index-offline.html` that works from
  `file://`, plus a **service worker** for an installable, offline-capable PWA.
- **Live reload** over server-sent events; in cloud mode, content syncs via a
  periodic `git pull` and an optional GitHub webhook (see [Updating](#updating)).

### Look & feel

- **i18n**: French and English. A single, carefully-built dark theme, re-skinnable
  via a CSS [extension](#extensions).

## Requirements

- **Python >= 3.11** (standard library only; uses `tomllib`, added in 3.11;
  developed on 3.12+).
- **A git repository for your content** (the mind). For cloud mode with content
  sync, a GitHub repository.
- **Somewhere to run it** — your machine for local use, or any host that can run
  a Python process or a container for self-hosting.
- No database. Accounts, tokens and share links are stored as plain JSON under
  `.atlas/`. (`bcrypt` is an optional dependency, used only to verify legacy
  `$2…` password hashes; the native scheme is scrypt, from the standard library.)

## Install

Installing puts a self-contained `atlas` command on your PATH (the viewer assets
ship inside the package — no separate download).

**The easy way — [uv](https://docs.astral.sh/uv/) or [pipx](https://pipx.pypa.io/).**
Both install the command into its own isolated environment (no virtualenv to
manage), and `uv` will even fetch a suitable Python for you:

```bash
uv tool install atlas-mind     # or: pipx install atlas-mind
atlas serve ~/my-mind

# Or run it once without installing, straight from PyPI:
uvx atlas-mind serve ~/my-mind   # or: pipx run atlas-mind serve ~/my-mind
```

**With pip** (a virtualenv is recommended):

```bash
pip install atlas-mind
# or, straight from the repository:
pip install "git+https://github.com/Anakior/atlas-mind.git"
# or, for development (editable, from a clone):
git clone https://github.com/Anakior/atlas-mind.git && cd atlas-mind && pip install -e .
```

Without installing, the engine also runs straight from the source tree as
`python3 src/cli.py <command>`. All examples below use the `atlas` command;
substitute `python3 src/cli.py` if you did not install. (`atlas-mind` is a
built-in alias of `atlas`, so the `uvx`/`pipx run` one-liners above resolve a
command without `--from`.)

## Quick start

Scaffold a new mind and serve it locally:

```bash
atlas init ~/my-mind
atlas serve ~/my-mind
```

`init` creates `atlas.toml`, a `.gitignore`, example documents under `content/`,
the AI-native scaffolding (`AGENTS.md`, `content/agents/`, `content/ai-sessions/`),
an empty `.atlas/extensions/` hook directory, and runs `git init -b main`. On a
terminal it asks a few questions (interface language, brand prefix, tagline) —
pass `--lang`, `--prefix`, `--tagline`, or `--yes` to skip the prompts. It never
overwrites existing files: it refuses a non-empty directory unless you pass
`--force`, and keeps any file already present.

`serve` builds the viewer once if it is missing, then becomes the HTTP server.
By default it listens on `127.0.0.1:8765` with authentication disabled — open
<http://127.0.0.1:8765> and your notes are there. Stop it with Ctrl+C.

To produce the static viewer without serving:

```bash
atlas build ~/my-mind            # dist/index.html (online shell)
atlas build ~/my-mind --offline  # dist/index-offline.html (monolith)
```

### CLI commands

Every command takes the mind directory as a positional argument; it **defaults to
the current directory**, so you can omit it from inside the mind.

| Command | Purpose |
|---|---|
| `init <dir> [--force] [--lang en\|fr] [--prefix P] [--tagline T] [--yes]` | Scaffold a new mind (never destructive; prompts for personalization on a TTY). |
| `serve <dir> [--port N]` | Build if needed, then run the HTTP server. |
| `build <dir> [--offline]` | Build the static viewer (`--offline` for the monolith). |
| `deploy <dir> [--target compose\|systemd\|fly] [--app NAME] [--wizard]` | Scaffold deployment files + guide; `fly --wizard` deploys end-to-end. |
| `user add <dir> --email <e> [--role admin\|viewer] [--password ...]` | Create an account in the file store. |
| `user list <dir>` | List accounts (and token state for API accounts). |
| `user remove <dir> --email <e>` | Remove an account. |
| `token create <dir> [--label claude]` | Mint a 256-bit API/MCP token (shown once). |
| `token list <dir>` | List API tokens (label, email, active/revoked, date). |
| `token revoke <dir> [--label claude]` | Revoke a token (idempotent). |
| `share list <dir>` | List all share links (active / expired / revoked). |
| `share revoke <dir> --id <id>` | Revoke a share link. |

The `user`, `token` and `share` commands operate on the local file store
(`.atlas/` by default). `token create` derives the account email from the label
(`claude` → `claude@api.local`) and prints the token once, with both the REST
(`Authorization: Bearer`) and MCP (`/mcp/<token>`) usage.

## Use it with your AI (MCP)

Atlas Mind is designed to be the external memory your AI assistant reads and
writes. `atlas init` scaffolds an **`AGENTS.md`** at the mind root (read by Claude
Code and AGENTS.md-aware tools) that tells the assistant how to use your mind —
adapt it to your conventions. Two folders come scaffolded: `content/agents/`
(reusable AI agent definitions) and `content/ai-sessions/` (session saves, with a
`TEMPLATE.md` template).

The engine exposes an **MCP endpoint** with seven tools — `search_docs`,
`read_doc`, `list_tree`, `recent_docs`, `create_doc`, `edit_doc`, `move_doc`.
Mint a token and point your assistant at it:

```bash
atlas token create ~/my-mind --label claude
# → prints the MCP URL: https://<your-atlas>/mcp/<token>
```

Your AI can then search, read and create documents directly in your mind — the
**Mind** view is, quite literally, the map it walks.

## Hive Mind (Atlas nodes)

Two Atlas Mind instances can share a slice of their content becoming an Hive Mind. The model is a
**personal memory with sharing**, not a real-time collaborative editor: a node is
published by one side and **mirrored read-only** by the other, kept in sync.

It is **pull-asymmetric** — only the *publisher* needs to be reachable over the
network; the subscriber just pulls. From the admin Settings → **Nodes** panel (or
the share-as-node button on any folder/file in the tree):

- **Publish a node.** Pick a folder or a single document and publish it. You get a
  one-time, copyable link (`atlas-node:…`) that carries the origin URL and a
  read-only token (stored hashed; the link is shown only once, re-issue to rotate).
- **Subscribe.** On the other instance, paste the link. Atlas Mind fetches a
  manifest, downloads the documents, and keeps a **read-only mirror** under
  `remotes/<node>/`. It re-syncs automatically (manifest diff by SHA-256) on the
  regular pull cadence, plus an on-demand **Sync** button. The publisher being
  offline just leaves the last copy in place.
- **Browse it like your own.** Mirrored documents appear in the tree under a
  distinct **“Mental nodes”** umbrella (with the source instance shown), and as
  their own teal, dashed regions in **The Mind**. They are read-only: editing,
  renaming, moving or deleting them is refused (it would be overwritten on the next
  sync anyway).
- **Make it yours.** “Appropriate” copies a node (or, from a multi-file node, just
  the current document) into your own documents at a destination you choose — a
  detached, fully editable copy.
- **Your AI sees everything.** Mirrored content lives under your `content/`, so the
  MCP/REST tools read it like any other document.

Nodes are managed by admins; tokens are scoped to the published path and can be
revoked at any time (which removes the subscriber's access at the next sync).

## Configuration

Configuration lives in `atlas.toml` at the root of the mind; every key is
optional. Precedence is **environment variable > `atlas.toml` > built-in
default**. Configuration is read once at startup. Unknown keys produce a
non-fatal warning on stderr (with a `difflib` suggestion); a wrong-typed value is
a fatal, readable error.

A minimal `atlas.toml` (roughly what `init` scaffolds):

```toml
# prefix = "Acme"          # "Acme" -> "Acme Atlas Mind"; empty/absent -> "Atlas Mind"
tagline = "Personal knowledge base."
lang = "en"                # "fr" or "en"

[server]
# port = 8765
# auth_enabled = false     # true = cloud mode (login required); needs session_secret
# session_secret = ""      # long random secret, REQUIRED if auth_enabled
# trusted_ip_header = "X-Forwarded-For"

[store]
kind = "file"              # local JSON under .atlas/ (no database)
# dir = ".atlas"

[todo]
# file = "notes/quick.md"
# categories = ["work", "personal"]

[build]
# excluded_names = ["drafts", "quick.md"]
```

### Key reference

The `prefix` key only changes the displayed name; the **Atlas Mind** wordmark
itself is fixed (see [Licence](#licence)). Env semantics: keys marked *presence*
take effect when the variable is set (even empty); keys marked *or* fall back to
`atlas.toml` when the variable is empty.

| `atlas.toml` key | Env var | Env semantics | Default | Notes |
|---|---|---|---|---|
| `prefix` (root) | — | — | `""` | Prefix before the fixed `Atlas Mind` wordmark. |
| `tagline` (root) | — | — | `Personal knowledge base.` | Home page tagline. |
| `lang` (root) | — | — | `en` | `en` or `fr` only. |
| `[server] port` | `PORT` | presence | `8765` | Validated 0–65535. |
| `[server] auth_enabled` | `KB_AUTH_ENABLED` | presence | `false` | Any non-empty value enables; empty disables. |
| `[server] session_secret` | `SESSION_SECRET` | presence | `dev-secret-change-me` | Cloud mode refuses to boot on the default. |
| `[server] session_max_age` | `SESSION_MAX_AGE` | presence | `2592000` (30 d) | Must be > 0. |
| `[server] git_pull_interval` | `GIT_PULL_INTERVAL` | presence | `300` | Seconds between content pulls; must be ≥ 0. |
| `[server] github_webhook_secret` | `GITHUB_WEBHOOK_SECRET` | presence | `""` | HMAC secret for the GitHub webhook. |
| `[server] trusted_ip_header` | `ATLAS_TRUSTED_IP_HEADER` | or | `""` (→ none) | Client-IP header behind a proxy. |
| `[store] kind` | `ATLAS_STORE` | or | `file` | Storage backend (local JSON). |
| `[store] dir` | `ATLAS_STORE_DIR` | or | `<mind>/.atlas` | Cannot be the mind root or under `content/`. |
| `[git] author_name` | — | — | `Atlas Bot` | Commit author for the bot. |
| `[git] author_email` | — | — | `atlas-bot@example.com` | Commit author email for the bot. |
| `[git] repo_url` | `GITHUB_REPO_URL` | or | none | Content repo URL **with the push token embedded** (used for the boot clone AND the content push-back) — see below. |
| `[todo] file` | — | — | `notes/quick.md` | Todo file, relative to `content/`. |
| `[todo] categories` | — | — | `["work", "personal"]` | First is the default category. |
| `[build] excluded_names` | — | — | `{"quick.md"}` | Names hidden from the viewer (dotfiles always excluded). |

`ATLAS_MIND` (and, in cloud mode only, `KB_REPO_PATH`, default `/app/repo`) select
the mind root. Resolution order: explicit `--dir`, then `ATLAS_MIND`, then
`KB_REPO_PATH` if `KB_AUTH_ENABLED` is set, otherwise the parent of `src/`.

## Updating

Engine and content are decoupled, so there are two independent update channels.

### Updating content (everyday)

Edit your documents and push to your mind's git repository. In cloud mode the
running instance picks the change up by **two means**: a periodic `git pull`
every `GIT_PULL_INTERVAL` seconds (default **300 s = 5 min**), and an instant
**GitHub webhook** (`POST /webhook/github`, HMAC-verified with
`GITHUB_WEBHOOK_SECRET`) for push-to-update. On each sync the instance rebuilds
the viewer and the open page **live-reloads** over SSE.

So a freshly-pushed note appears within five minutes by default, or immediately
if the webhook is configured. Lower `GIT_PULL_INTERVAL` for snappier polling, or
rely on the webhook and raise it. (Edits made *through* the viewer are committed
and pushed by the instance itself, so they need no manual sync.)

### The GitHub push token

In cloud mode the instance both **clones** your private mind repo at boot and
**pushes** content writes back to it. Both use a single setting: the repo URL
**with the token embedded**, named `GITHUB_REPO_URL`:

```
GITHUB_REPO_URL="https://x-access-token:<TOKEN>@github.com/<you>/<your-mind>.git"
```

`<TOKEN>` is a GitHub **fine-grained personal access token** with **Contents:
Read and write** on that one repo. Create it at **GitHub → your avatar →
Settings → Developer settings → Personal access tokens → Fine-grained tokens →
Generate new token** (direct link:
`https://github.com/settings/personal-access-tokens/new`): under *Repository
access* pick *Only select repositories* → your mind repo, then under
*Permissions → Repository permissions* set **Contents → Read and write**. The
token (`github_pat_…`) is shown once — copy it.

Where to set `GITHUB_REPO_URL` depends on the host: **Fly** →
`fly secrets set GITHUB_REPO_URL="…"`; **Docker Compose** → a line in `.env`;
**systemd** → `Environment=GITHUB_REPO_URL="…"`. It is a secret (it carries the
token) — never commit it. The registry under `.atlas/` is gitignored, so the
token is never pushed with your content.

### Updating the engine

Engine updates are **not automatic** — only your *content* syncs on its own (the
git pull + webhook above). When a new engine version ships (the admin Settings
**update banner** flags it), update by hand. Content and the `.atlas`
store/volume are preserved; only the code changes.

The simplest way is the **`atlas update`** command — the mirror of `atlas deploy`.
It prints the right command for your target (and runs it with `--run`):

```bash
atlas update --target fly --app <your-app>   # or --target compose / --target systemd
atlas update --target compose --run          # build (no-cache) + up -d, in one go
```

Under the hood: the generated images install `atlas-mind` from PyPI, and **Docker
caches the `pip install` layer**, so a *plain* redeploy can silently reuse the old
version — `atlas update` forces a fresh fetch (`--no-cache`) for you. The manual
equivalents:

```bash
fly deploy -c deploy/fly.toml --no-cache              # Fly.io
docker compose build --no-cache && docker compose up -d   # Docker Compose
```

Cleaner and reproducible: **pin** the version in the Dockerfile
(`pip install "atlas-mind[bcrypt]==X.Y.Z"`) and bump it — a normal `fly deploy`
then rebuilds that layer because the Dockerfile changed. For a local install,
update in place — `uv tool upgrade atlas-mind`, `pipx upgrade atlas-mind`, or
`pip install -U atlas-mind` — and restart the service.

## Deployment

`atlas deploy <mind> --target <compose|systemd|fly>` scaffolds ready-to-use
deployment files into `<mind>/deploy/` and prints a step-by-step guide. The
local/cloud split is driven by `KB_AUTH_ENABLED`: any non-empty value enables
authentication (the cloud-mode default), empty disables it. When auth is on a
real `SESSION_SECRET` is mandatory — the server refuses to boot on the default.

- **Local (CLI)** — `atlas serve <mind>`: binds `127.0.0.1`, auth off, no secret
  required. The everyday local mode — nothing to deploy.
- **Fly.io — one command** — `atlas deploy <mind> --target fly --wizard` runs the
  whole thing interactively: creates the app + volume, generates and sets
  `SESSION_SECRET`, asks for your private content repo + token (`GITHUB_REPO_URL`),
  deploys, and creates the admin account — no setup token, no manual juggling.
  Drop `--wizard` to only scaffold `deploy/fly.toml` + `deploy/Dockerfile.fly` and
  follow the printed steps; `--app <name>` overrides the auto-derived app name.
  The mind is cloned from your private GitHub repo at boot and the account
  registry lives on a persistent volume.
- **Docker Compose** — `atlas deploy <mind> --target compose` scaffolds a
  `Dockerfile` + `docker-compose.yml` + `Caddyfile` + `.env.example` (engine
  installed from PyPI, your mind mounted, Caddy for automatic HTTPS): then
  `cd <mind>/deploy && cp .env.example .env`, set `SESSION_SECRET`, point the
  `Caddyfile` at your domain, and `docker compose up -d`.
- **systemd (no Docker)** — `atlas deploy <mind> --target systemd` scaffolds
  `deploy/atlas.service`, which runs `atlas serve` from a venv as a dedicated
  non-root user. Front it with a TLS reverse proxy.

Behind a reverse proxy, set `trusted_ip_header = "X-Forwarded-For"` (or the header
your proxy sets) so the login rate limiter sees the real client IP — the bundled
Caddyfile already sets it. The registry under `.atlas/` is gitignored and (on Fly)
lives on the volume, so it is never committed with your content.

## Building from source

Running and deploying Atlas needs no build step — the package ships ready to run.
The one exception is the viewer's stylesheet: `src/web/vendor/tailwind.css` is a
**precompiled** Tailwind bundle containing only the classes the viewer uses. If you
edit `src/web/viewer.html` and add a Tailwind class that was not already on the page,
it will not exist in the compiled CSS (a silent no-op) until you regenerate it:

```bash
cd src/web/tailwind
npm install      # once — dev-only toolchain, into a gitignored node_modules
npm run build    # after every change to classes in viewer.html
```

Commit the regenerated CSS with your HTML change. See [CONTRIBUTING.md](CONTRIBUTING.md)
for details; the toolchain is never bundled into the published package.

## Extensions

Atlas Mind has two minimal hooks rather than a plugin system, both anchored on a
fixed per-mind directory: `<mind>/.atlas/extensions/`. A missing or empty
directory means no extensions, and the viewer and server are byte-for-byte
unchanged.

- **Viewer assets** — at build time, `*.css` and `*.js` there are concatenated
  (alphabetical) and inlined into the viewer, in both online and offline builds,
  and re-injected into public share pages. Closing tags are neutralised so an
  extension cannot escape its container.
- **Server routes** — at boot, `*.py` modules are imported; each exposes
  `register(context)` and registers routes via `context.add_route(method,
  pattern, handler, role=...)` (`role` ∈ `public` / `auth` / `admin`).

A broken extension is logged and skipped — it never crashes the boot or fails a
build. The viewer exposes a small `window.Atlas` API and emits
`atlas:doc-rendered` / `atlas:edit-enter` DOM events. Two worked examples live in
`examples/extensions/`: **`custom-theme`** (re-skin the design via CSS) and
**`pob`** (a server route + viewer template + styles). Installing an extension
means copying the files in, rebuilding, and restarting the server.

## Security model

Authentication is implemented from scratch on the standard library. A brief,
honest summary:

- **Passwords**: native scrypt (N=2¹⁴, r=8, p=1, 16-byte salt, 64-byte output).
  bcrypt is an optional fallback only for legacy `$2…` hashes.
- **Sessions**: HMAC-SHA256–signed (not encrypted) cookies (email/role/epoch/ts),
  revocable server-side via a `session_epoch` bumped on logout-all, password
  reset and TOTP changes. `HttpOnly`, `SameSite=Lax`, `Secure` when auth is on.
- **CSRF** defence in depth: JSON content type required when there is a body,
  same-origin `Origin`/`Referer` check when present, and a synchronizer token
  (`HMAC(secret, "email|epoch")`) in an `X-CSRF-Token` header.
- **2FA**: TOTP (RFC 6238, ±1 step) with ten single-use recovery codes.
- **Brute-force**: per-account lockout with exponential backoff (60 s → 1 h) plus
  a per-IP login rate limit (10/min); timing equalised to avoid enumeration.
- **API / MCP tokens**: SHA-256 of a 256-bit secret, role `api`; REST writes are
  create-only; per-token rate limit 120/min.
- **Share links**: HMAC-signed and **fail-closed** when auth is on — an
  unreachable registry returns 503 rather than serving a possibly-revoked doc.

Known limitations, stated plainly: the **TOTP secret is stored in cleartext** in
the registry; the **session cookie payload is readable** (signed, not encrypted);
share-link revocation is only re-checked when `auth_enabled`; account lockout
state is per-instance; and `SESSION_SECRET` is used for sessions, CSRF, share
links and the webhook alike (cloud mode refuses the default secret).

## Licence

Atlas Mind is licensed under the **GNU Affero General Public License v3.0**
(AGPL-3.0). Because the AGPL covers use over a network, if you run a modified
version as a network service you must make the corresponding source available to
its users. See `LICENSE` for the full terms and `NOTICE` for the brand notice.

**Brand notice.** The name **Atlas Mind** and its visual wordmark (the styled
"Atlas" + "Mind" badge in the viewer and login page) are a fixed part of the
engine and are not granted to you by the software licence. The configurable
`prefix` key lets you display your own name in front (e.g. `"Acme"` → "Acme Atlas
Mind"); the `Atlas Mind` wordmark itself stays. You may fork and modify under the
AGPL, but please do not present a modified version as the official Atlas Mind, and
keep the brand notice intact.

Bundled third-party frontend libraries and fonts are vendored under their own
licences (MIT, Apache-2.0, BSD-3-Clause, OFL, …); see `src/web/vendor/LICENSES.md`.
