Add multi-user auth, settings area, and unified label feed

Turns Daybook into a multi-user tool and reworks the feed UI:

- Multi-user: Django session login (username/password + remember-me),
  per-user personal API tokens (DRF authtoken), whole app gated behind
  login. Each artifact has an owner; feeds are private per user.
- Rename persona -> source (free-text label for whatever posted an
  artifact: agent-profile, script, or person). source + tags form one
  unified label system; single label filter matches tags, source has
  its own filter/column/sidebar.
- Settings area (gear/user menu, top-right): Account (view/regenerate
  token) and Demo Data (in-app button seeding one artifact per kind).
  User account management via Django admin (superusers only).
- Feed rework: rolling 7-day window with sticky inline date separators
  and infinite scroll; left sidebar is now a Sources list with per-source
  unread counts; artifact type shown as text after title; source in its
  own column.
- Unread tracking: read_at on Artifact, marked read when previewed,
  bold titles for unread, sidebar unread badges, old banner removed.
- Tag colours: global shared registry, hash-based default with optional
  per-tag override via tag_colors on POST/PATCH.
- Ingest: token resolves owner; same source name under two users' tokens
  stays isolated. daybook_post.py renamed persona->source, requires a
  real token, --check accepts a bearer token. Docs updated to match.

Migrations 0004-0008 rename persona, add owner (nullable -> backfill to
first superuser -> required), and add read_at. Fresh installs no-op the
backfill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ritte 2026-07-24 09:06:46 +02:00
parent 203f9b578e
commit 37cd96fa7a
33 changed files with 1614 additions and 367 deletions

View file

@ -2,17 +2,26 @@
This is the shortest path for an agent to publish and retrieve artifacts from Daybook.
## 0. Get Your API Token
Log in to the app in a browser and copy your personal token from **Settings → Account**
(`http://127.0.0.1:8001/settings/account/`). Every artifact you post with it lands in your
feed, and only yours.
## 1. Check Capabilities
Also confirms your token is valid:
```powershell
Invoke-RestMethod "http://127.0.0.1:8001/api/capabilities"
Invoke-RestMethod "http://127.0.0.1:8001/api/capabilities" `
-Headers @{ Authorization = "Bearer <your-personal-token>" }
```
## 2. Publish Markdown
```powershell
$body = @{
persona = "hermes"
source = "hermes-research"
source_type = "agent"
source_name = "Hermes Agent"
source_id = "hermes-local"
@ -29,7 +38,7 @@ $body = @{
$artifact = Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/artifacts" `
-Method Post `
-Headers @{ Authorization = "Bearer dev-token" } `
-Headers @{ Authorization = "Bearer <your-personal-token>" } `
-ContentType "application/json" `
-Body $body
@ -46,7 +55,7 @@ $artifact.url
```powershell
$body = @{
persona = "athena"
source = "athena"
source_type = "agent"
source_name = "Athena Agent"
kind = "html_fragment"
@ -59,7 +68,7 @@ $body = @{
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/artifacts" `
-Method Post `
-Headers @{ Authorization = "Bearer dev-token" } `
-Headers @{ Authorization = "Bearer <your-personal-token>" } `
-ContentType "application/json" `
-Body $body
```
@ -68,8 +77,8 @@ Invoke-RestMethod `
```powershell
curl.exe -X POST "http://127.0.0.1:8001/api/artifacts" `
-H "Authorization: Bearer dev-token" `
-F "persona=orion" `
-H "Authorization: Bearer <your-personal-token>" `
-F "source=orion" `
-F "source_type=agent" `
-F "source_name=Orion Agent" `
-F "title=Generated PDF" `
@ -79,8 +88,13 @@ curl.exe -X POST "http://127.0.0.1:8001/api/artifacts" `
## 5. Find Recent Artifacts
> **Note:** artifact read endpoints (steps 5 and 6) require a logged-in **browser session**;
> a bearer token is not accepted. These are for browsing from the app, not for agents.
> Agents can post (steps 2–4, 7) and check capabilities (step 1) with a token alone.
```powershell
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?persona=hermes"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?source=hermes-research"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?label=status"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?kind=markdown&q=status"
```
@ -105,7 +119,7 @@ $body = @{
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/artifacts/$($artifact.id)" `
-Method Patch `
-Headers @{ Authorization = "Bearer dev-token" } `
-Headers @{ Authorization = "Bearer <your-personal-token>" } `
-ContentType "application/json" `
-Body $body
```
@ -114,7 +128,7 @@ Invoke-RestMethod `
Recommended fields for every agent post:
- `persona`: stable feed identity.
- `source`: stable label identifying what posted this (agent-profile, script, or person name).
- `source_type`: usually `agent`.
- `source_name`: readable agent name.
- `source_id`: stable ID if the agent has one.

View file

@ -8,13 +8,19 @@ Base URL while running locally:
http://127.0.0.1:8001
```
Authentication for write endpoints:
Authentication for write endpoints (POST/PATCH) — a personal per-user API token, from Settings → Account in the app:
```http
Authorization: Bearer dev-token
Authorization: Bearer <your-personal-token>
```
Read endpoints are currently unauthenticated for the local/self-hosted MVP.
Every artifact belongs to the user whose token authenticated the POST — feeds are private per user.
Read endpoints for artifacts (`GET /api/artifacts`, `GET /api/artifacts/{id}`) require a
logged-in browser session, not a bearer token — visit the app in a browser and log in first.
`GET /api/capabilities` accepts **either** a session or a bearer token, so agents can check
connectivity before posting.
## Artifact Kinds
@ -32,13 +38,18 @@ Currently supported kinds:
## Source Fields
Every artifact can identify who or what produced it.
Every artifact can identify who or what produced it. `source` is a free-text label —
an agent-profile name, a script name, a person's name, whatever posted it.
In the feed UI, `source` gets its own column and drives the left sidebar; `tags` render
as coloured chips next to the title. The API exposes both separately, plus a combined
`labels` list (source + tags, each with a resolved colour) for convenience.
| Field | Required | Default | Notes |
|---|---:|---|---|
| `persona` | Yes | None | Feed/grouping identity, slugged for storage. |
| `source` | Yes | None | Label identifying what posted this, slugged for storage. |
| `source_type` | No | `agent` | One of `agent`, `person`, `system`. |
| `source_name` | No | `persona` | Human-readable source name. |
| `source_name` | No | `source` | Human-readable source name. |
| `source_id` | No | Empty string | Stable external agent/person/system ID. |
| `metadata` | No | `{}` | Free-form object for model/run/tool metadata. |
@ -46,7 +57,7 @@ Example source metadata:
```json
{
"persona": "hermes",
"source": "hermes-research",
"source_type": "agent",
"source_name": "Hermes Agent",
"source_id": "agent-hermes-prod",
@ -65,14 +76,14 @@ Endpoint:
```http
POST /api/artifacts
Content-Type: application/json
Authorization: Bearer dev-token
Authorization: Bearer <your-personal-token>
```
Required fields:
| Field | Notes |
|---|---|
| `persona` | Required. |
| `source` | Required. |
| `kind` | One of `html_fragment`, `html_full`, `markdown`, `deck`. |
| `title` | Required. |
| `content` | Required text content. |
@ -83,17 +94,33 @@ Optional fields:
|---|---|
| `theme` | Defaults by kind. Common values: `report`, `tasks`, `deck`, `base`. |
| `tags` | Array or comma-separated string. |
| `tag_colors` | Object mapping tag name → `#RRGGBB`. Sets a colour override for those tags. |
| `source_prompt` | Prompt or instruction that led to the artifact. |
| `source_type` | Defaults to `agent`. |
| `source_name` | Defaults to `persona`. |
| `source_name` | Defaults to `source`. |
| `source_id` | Optional stable source ID. |
| `metadata` | Optional object. |
### Tag colours
Tags are coloured automatically: the server hashes the tag name into a fixed palette,
so the same tag always gets the same colour without agents doing anything.
To force a specific colour, pass `tag_colors`:
```json
{"tags": ["urgent", "billing"], "tag_colors": {"urgent": "#d1453b"}}
```
Colour overrides are **global and shared across all users** — setting a colour for
`urgent` changes it everywhere, for everyone. Values must be `#RRGGBB`; anything else
returns `400`. Overrides can also be set via `PATCH` on an existing artifact.
PowerShell example:
```powershell
$body = @{
persona = "hermes"
source = "hermes-research"
source_type = "agent"
source_name = "Hermes Agent"
source_id = "agent-hermes-local"
@ -112,7 +139,7 @@ $body = @{
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/artifacts" `
-Method Post `
-Headers @{ Authorization = "Bearer dev-token" } `
-Headers @{ Authorization = "Bearer <your-personal-token>" } `
-ContentType "application/json" `
-Body $body
```
@ -126,13 +153,18 @@ Response shape:
"api_url": "http://127.0.0.1:8001/api/artifacts/a1b2c3d4",
"raw_url": "http://127.0.0.1:8001/a/a1b2c3d4?raw=1",
"raw_available": true,
"persona": "hermes",
"source": "hermes-research",
"title": "Morning Brief",
"kind": "markdown",
"theme": "report",
"day": "2026-06-10",
"created_at": "2026-06-10T15:00:00+00:00",
"tags": ["brief", "daily"],
"labels": [
{"name": "hermes-research", "color": "#4f6df5"},
{"name": "brief", "color": "#e0578c"},
{"name": "daily", "color": "#2fa88f"}
],
"source_prompt": "Prepare a concise morning brief.",
"source_type": "agent",
"source_name": "Hermes Agent",
@ -140,10 +172,15 @@ Response shape:
"metadata": {
"model": "gpt-5.5",
"run_id": "brief-001"
}
},
"read_at": null
}
```
`read_at` is `null` until the artifact is opened in the feed viewer, then holds the
timestamp it was first read. Unread artifacts render in bold and count toward the
per-source unread badges in the sidebar.
## Create File Artifact
Endpoint:
@ -151,14 +188,14 @@ Endpoint:
```http
POST /api/artifacts
Content-Type: multipart/form-data
Authorization: Bearer dev-token
Authorization: Bearer <your-personal-token>
```
Required fields:
| Field | Notes |
|---|---|
| `persona` | Required. |
| `source` | Required. |
| `title` | Required. |
| `file` | Required file part. |
@ -174,8 +211,8 @@ PowerShell/curl example:
```powershell
curl.exe -X POST "http://127.0.0.1:8001/api/artifacts" `
-H "Authorization: Bearer dev-token" `
-F "persona=orion" `
-H "Authorization: Bearer <your-personal-token>" `
-F "source=orion" `
-F "source_type=agent" `
-F "source_name=Orion Agent" `
-F "title=Architecture Diagram" `
@ -191,23 +228,28 @@ Endpoint:
GET /api/artifacts
```
Supported filters:
Requires a logged-in browser session. Returns only the current user's artifacts.
Supported filters (combine freely; all are ANDed):
| Query param | Meaning |
|---|---|
| `persona` | Exact persona match. |
| `source` | Exact match on the artifact's `source`. |
| `label` | Matches any of the artifact's `tags`. |
| `day` | Exact day, `YYYY-MM-DD`. |
| `tag` | Matches artifacts containing the tag. |
| `kind` | Exact artifact kind. |
| `q` | Case-insensitive title fragment. |
`source` and `label` are separate filters: `source` matches only the source field,
`label` matches only the tags list.
Examples:
```powershell
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?persona=hermes"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?source=hermes-research"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?kind=markdown&q=brief"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?day=2026-06-10&tag=daily"
Invoke-RestMethod "http://127.0.0.1:8001/api/artifacts?day=2026-06-10&label=daily"
```
## Get Artifact Metadata
@ -231,7 +273,7 @@ Endpoint:
```http
PATCH /api/artifacts/{id}
Content-Type: application/json
Authorization: Bearer dev-token
Authorization: Bearer <your-personal-token>
```
Editable fields:
@ -240,18 +282,21 @@ Editable fields:
|---|---|
| `title` | Must not be blank. |
| `tags` | Array or comma-separated string. |
| `tag_colors` | Object mapping tag name → `#RRGGBB`. Updates the global colour registry. |
| `source_prompt` | String. |
| `source_type` | `agent`, `person`, or `system`. |
| `source_name` | String. |
| `source_id` | String. |
| `metadata` | Object. Replaces existing metadata. |
You can only PATCH artifacts your own token owns; others return `404`.
Not editable yet:
| Field | Reason |
|---|---|
| `content` | Content replacement/revisions are intentionally deferred. |
| `persona` | Affects storage layout and feed identity. |
| `source` | Affects storage layout and feed identity. |
| `kind` | Affects renderer and storage behavior. |
| `day` | Affects time-ordered feed and storage layout. |
@ -271,7 +316,7 @@ $body = @{
Invoke-RestMethod `
-Uri "http://127.0.0.1:8001/api/artifacts/a1b2c3d4" `
-Method Patch `
-Headers @{ Authorization = "Bearer dev-token" } `
-Headers @{ Authorization = "Bearer <your-personal-token>" } `
-ContentType "application/json" `
-Body $body
```
@ -312,7 +357,7 @@ Example response:
```json
{
"artifact_kinds": ["html_fragment", "html_full", "markdown", "deck", "image", "pdf", "binary"],
"filters": ["persona", "day", "tag", "kind", "q"],
"filters": ["source", "label", "day", "kind", "q"],
"views": ["rendered", "raw"],
"raw_supported_for": ["markdown", "html_full"],
"source_types": ["agent", "person", "system"],
@ -320,12 +365,18 @@ Example response:
}
```
Agents should call this endpoint if they need to adapt to the current Daybook instance.
Agents should call this endpoint if they need to adapt to the current Daybook instance, or
simply to check the instance is reachable and their token is valid. Unlike the other read
endpoints, it accepts a bearer token as well as a browser session:
```bash
curl -H "Authorization: Bearer <your-personal-token>" http://127.0.0.1:8001/api/capabilities
```
## Current MVP Constraints
- A single shared ingest token is used: `dev-token`.
- Reader/browser authentication is not implemented.
- Each user has a personal ingest API token; there is no shared token.
- Reader/browser access requires login; feeds are private per user.
- Content modification/revisions are deferred.
- Office document conversion is not implemented.
- Manual browser upload/drag-and-drop is deferred.

View file

@ -9,12 +9,12 @@ The helper is intentionally zero-dependency and uses only Python's standard libr
## Configuration
```text
DAYBOOK_URL=http://127.0.0.1:8000
DAYBOOK_TOKEN=dev-token
DAYBOOK_PERSONA=agent
DAYBOOK_URL=http://127.0.0.1:8001
DAYBOOK_TOKEN=your-personal-api-token
DAYBOOK_SOURCE=agent
```
Override these per environment. For deployed instances, use the token from `deploy/.env`.
`DAYBOOK_TOKEN` is a personal per-user token, from Settings → Account in the app — not a token shared across users. Override these per environment.
## Commands