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:
parent
203f9b578e
commit
37cd96fa7a
33 changed files with 1614 additions and 367 deletions
|
|
@ -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.
|
||||
|
|
|
|||
111
docs/api.md
111
docs/api.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue