diff --git a/.claude/commands/seed-demo.md b/.claude/commands/seed-demo.md
index ccca3ce..18145b7 100644
--- a/.claude/commands/seed-demo.md
+++ b/.claude/commands/seed-demo.md
@@ -8,6 +8,6 @@ Run the demo seeding script to populate today with one tagged sample artifact pe
python scripts/seed_demo_artifacts.py
```
-Requires the Daybook dev server to be running on the project's fixed local port: `python manage.py runserver 127.0.0.1:8001`. The script defaults to `DAYBOOK_URL=http://127.0.0.1:8001` / `DAYBOOK_TOKEN=dev-token`; no overrides needed unless the user has changed these.
+Requires the AgentFeed dev server to be running on the project's fixed local port: `python manage.py runserver 127.0.0.1:8001`. The script defaults to `AGENTFEED_URL=http://127.0.0.1:8001` / `AGENTFEED_TOKEN=dev-token`; no overrides needed unless the user has changed these.
After running, report each posted artifact's kind and URL, and remind the user to reload the feed for today.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 801f2a6..8acf954 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,32 @@
# Changelog
-All notable changes to Daybook are recorded here. Format loosely follows
+All notable changes to AgentFeed are recorded here. Format loosely follows
[Keep a Changelog](https://keepachangelog.com/); versions are SemVer and match
-the git tags (`vX.Y.Z`) and `daybook_project.__version__`.
+the git tags (`vX.Y.Z`) and `__version__`. (Entries before 0.3.0 use the app's
+former name, "Daybook".)
## [Unreleased]
_Nothing yet._
+## [0.3.0] — 2026-09-04
+
+### Changed
+- **Renamed the app to AgentFeed.** The former name "Daybook" is removed
+ from everything user- and agent-facing: the UI brand, docs/README, the
+ posting skill (`agentfeed-posting` / `agentfeed_post.py`), CSS
+ (`agentfeed.css`), localStorage keys, and the ingest **environment
+ variables** — `DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/...` are now
+ `AGENTFEED_*`. Agents must update their env-var names (the token *values*
+ are unchanged). The Django package `daybook_project`, the `/opt/…` install
+ path, and the git repo keep their names for now (a later, separate step).
+- Key-provisioning API (`POST /api/keys`) is now **disabled by default**,
+ independent of `DEBUG`; enable it explicitly with
+ `AGENTFEED_KEY_PROVISIONING_API=1` on a test install only.
+
+### Fixed
+- Posting helper now points at Settings → API Keys (was Settings → Account).
+
## [0.2.1] — 2026-09-04
### Added
@@ -98,7 +117,8 @@ First tagged release.
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
runbook (`deploy/cloud_agent_deploy.md`) and health check.
-[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.2.1...HEAD
+[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.3.0...HEAD
+[0.3.0]: https://git.cygnus.systems/peter/daybook/compare/v0.2.1...v0.3.0
[0.2.1]: https://git.cygnus.systems/peter/daybook/compare/v0.2.0...v0.2.1
[0.2.0]: https://git.cygnus.systems/peter/daybook/compare/v0.1.1...v0.2.0
[0.1.1]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...v0.1.1
diff --git a/CLAUDE.md b/CLAUDE.md
index f612d1d..c0fa33e 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2,7 +2,7 @@
## Project
-Daybook is a self-hosted, multi-user Django + Django REST Framework web app where AI agents, scripts, or people publish artifacts and each logged-in user browses their own time-ordered, label-filterable feed.
+AgentFeed is a self-hosted, multi-user Django + Django REST Framework web app where AI agents, scripts, or people publish artifacts and each logged-in user browses their own time-ordered, label-filterable feed.
The core product principle is: agents publish content, not files. The server owns IDs, styling, storage layout, dates, and normalization into browser-native HTML, image, or PDF outputs.
@@ -44,7 +44,7 @@ The core product principle is: agents publish content, not files. The server own
- Use the local Django commands for normal iteration: `python manage.py check`, `python manage.py migrate`, and `python manage.py runserver`.
- Run the local development server on `127.0.0.1:8001`. Port `8000` may be reserved or blocked on this workstation.
- Always use this same fixed port for the dev server. Never start a second instance on a different port; if `8001` is already serving, reuse it rather than starting another.
-- All artifact-posting tools (`skills/daybook-posting/scripts/daybook_post.py`, `scripts/seed_demo_artifacts.py`, and any future posting skill) must default `DAYBOOK_URL` to `http://127.0.0.1:8001` to match.
+- All artifact-posting tools (`skills/agentfeed-posting/scripts/agentfeed_post.py`, `scripts/seed_demo_artifacts.py`, and any future posting skill) must default `AGENTFEED_URL` to `http://127.0.0.1:8001` to match.
- A regular (non-superuser) user `test` / password `testpwd` must always exist in the local dev database — it's the permanent account a sibling project (`AGENT_DAYBOOK__LLM_TEST`) uses for API testing. If the local `db.sqlite3` is ever reset, recreate this exact user (e.g. via Settings → Users) before assuming that project is broken.
- Do not deploy to the Proxmox NUC during routine code iteration.
- Treat the NUC container as the local live/staging target. Deploy there only at explicit stable checkpoints, such as an end-of-day build.
diff --git a/README.md b/README.md
index d3adb1c..a2bacf2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Daybook
+# AgentFeed
-Daybook is a self-hosted, multi-user **artifact journal**. AI agents, scripts,
+AgentFeed is a self-hosted, multi-user **artifact journal**. AI agents, scripts,
or people publish finished outputs — reports, HTML, images, PDFs, decks, JSON,
CSV — and each logged-in user browses their own time-ordered, label-filterable
feed. The core principle: **agents publish content, not files.** The server
@@ -35,9 +35,9 @@ Then open and log in.
|---|---|
| [`docs/agent_quickstart.md`](docs/agent_quickstart.md) | Shortest path to posting from an agent |
| [`docs/api.md`](docs/api.md) | Full REST API reference (kinds, endpoints, keys, sharing) |
-| [`docs/llm_posting_helper.md`](docs/llm_posting_helper.md) | Using the bundled `daybook_post.py` helper |
+| [`docs/llm_posting_helper.md`](docs/llm_posting_helper.md) | Using the bundled `agentfeed_post.py` helper |
| [`docs/deployment.md`](docs/deployment.md) | Docker deployment, reverse proxy, `/admin/`, backup |
-| [`skills/daybook-posting/SKILL.md`](skills/daybook-posting/SKILL.md) | The agent-posting skill (also served at `/api/skill`) |
+| [`skills/agentfeed-posting/SKILL.md`](skills/agentfeed-posting/SKILL.md) | The agent-posting skill (also served at `/api/skill`) |
| [`CHANGELOG.md`](CHANGELOG.md) | Release history |
## Admin
diff --git a/artifacts/exception_handlers.py b/artifacts/exception_handlers.py
index fa09eba..205752b 100644
--- a/artifacts/exception_handlers.py
+++ b/artifacts/exception_handlers.py
@@ -2,7 +2,7 @@ from rest_framework.response import Response
from rest_framework.views import exception_handler
-def daybook_exception_handler(exc, context):
+def agentfeed_exception_handler(exc, context):
"""DRF exception handler that additionally maps pathological-input crashes
to a clean 400 instead of an unhandled 500.
diff --git a/artifacts/forms.py b/artifacts/forms.py
index 44f8e98..52f2d29 100644
--- a/artifacts/forms.py
+++ b/artifacts/forms.py
@@ -2,5 +2,5 @@ from django import forms
from django.contrib.auth.forms import AuthenticationForm
-class DaybookLoginForm(AuthenticationForm):
+class AgentFeedLoginForm(AuthenticationForm):
remember_me = forms.BooleanField(required=False, initial=True)
diff --git a/artifacts/models.py b/artifacts/models.py
index e77f143..bbd14a8 100644
--- a/artifacts/models.py
+++ b/artifacts/models.py
@@ -5,7 +5,7 @@ from django.db import models
def generate_api_key():
- """`dbk_` prefix (recognizable as a Daybook key) + 40 hex chars."""
+ """`dbk_` prefix (recognizable as a AgentFeed key) + 40 hex chars."""
return "dbk_" + secrets.token_hex(20)
diff --git a/artifacts/templatetags/daybook_extras.py b/artifacts/templatetags/agentfeed_extras.py
similarity index 100%
rename from artifacts/templatetags/daybook_extras.py
rename to artifacts/templatetags/agentfeed_extras.py
diff --git a/artifacts/views.py b/artifacts/views.py
index aa3bfd0..d1978ed 100644
--- a/artifacts/views.py
+++ b/artifacts/views.py
@@ -34,7 +34,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from .demo_data import create_demo_artifacts
-from .forms import DaybookLoginForm
+from .forms import AgentFeedLoginForm
from .models import ApiKey, Artifact, Asset, TagColor
from .storage import (
QuotaExceeded,
@@ -66,7 +66,7 @@ REMEMBER_ME_SESSION_AGE = 60 * 60 * 24 * 30 # 30 days
MAX_ASSET_BYTES = 50 * 1024 * 1024 # 50 MB per uploaded image
-# Matches the asset id in a Daybook asset URL, e.g. /assets/ab12cd34ef56
+# Matches the asset id in a AgentFeed asset URL, e.g. /assets/ab12cd34ef56
ASSET_URL_RE = re.compile(r"/assets/([0-9a-f]{12})")
@@ -315,7 +315,7 @@ class CapabilitiesView(APIView):
"/api/capabilities": ["GET"],
"/api/skill": ["GET"],
}
- if settings.DAYBOOK_KEY_PROVISIONING_API:
+ if settings.AGENTFEED_KEY_PROVISIONING_API:
methods["/api/keys"] = ["GET", "POST"]
methods["/api/keys/{id}"] = ["DELETE"]
@@ -330,30 +330,30 @@ class CapabilitiesView(APIView):
"auth": "bearer_token",
"methods": methods,
"skill_url": request.build_absolute_uri(reverse("skill-api")),
- "key_provisioning_api": settings.DAYBOOK_KEY_PROVISIONING_API,
+ "key_provisioning_api": settings.AGENTFEED_KEY_PROVISIONING_API,
}
)
-SKILL_MD_PATH = settings.BASE_DIR / "skills" / "daybook-posting" / "SKILL.md"
+SKILL_MD_PATH = settings.BASE_DIR / "skills" / "agentfeed-posting" / "SKILL.md"
def render_posting_skill(origin):
- """Return the bundled posting SKILL.md with its DAYBOOK_URL placeholder
+ """Return the bundled posting SKILL.md with its AGENTFEED_URL placeholder
rewritten to this instance's origin, so text handed to an agent already
points at the right server. Only the config-block assignment is touched;
- the surrounding prose (which explains DAYBOOK_URL) is left alone."""
+ the surrounding prose (which explains AGENTFEED_URL) is left alone."""
text = SKILL_MD_PATH.read_text(encoding="utf-8")
return re.sub(
- r"(?m)^(DAYBOOK_URL=)\S+",
+ r"(?m)^(AGENTFEED_URL=)\S+",
lambda m: m.group(1) + origin,
text,
)
class SkillView(APIView):
- """Serve the bundled agent-posting skill (skills/daybook-posting/SKILL.md),
- with DAYBOOK_URL pre-filled to this instance so a recipient posts to the
+ """Serve the bundled agent-posting skill (skills/agentfeed-posting/SKILL.md),
+ with AGENTFEED_URL pre-filled to this instance so a recipient posts to the
right server. Same auth as capabilities: a logged-in user can copy it, or
an agent can fetch it with its API token.
@@ -390,7 +390,7 @@ def serialize_api_key(api_key):
class ApiKeyListCreateView(APIView):
"""Programmatic API-key management, for automated test installations that
- provision their own agents. Gated by settings.DAYBOOK_KEY_PROVISIONING_API,
+ provision their own agents. Gated by settings.AGENTFEED_KEY_PROVISIONING_API,
which is OFF in deployment (it hands out key material over the API). When
off, every method returns 403. Authenticated by session or an existing
bearer token, and only ever operates on that user's own keys.
@@ -405,7 +405,7 @@ class ApiKeyListCreateView(APIView):
return resolve_token_user(request)
def get(self, request):
- if not settings.DAYBOOK_KEY_PROVISIONING_API:
+ if not settings.AGENTFEED_KEY_PROVISIONING_API:
return Response({"detail": "Key provisioning API is disabled on this instance."}, status=403)
user = self._resolve_user(request)
if user is None:
@@ -413,7 +413,7 @@ class ApiKeyListCreateView(APIView):
return Response([serialize_api_key(k) for k in user.api_keys.all()])
def post(self, request):
- if not settings.DAYBOOK_KEY_PROVISIONING_API:
+ if not settings.AGENTFEED_KEY_PROVISIONING_API:
return Response({"detail": "Key provisioning API is disabled on this instance."}, status=403)
user = self._resolve_user(request)
if user is None:
@@ -432,7 +432,7 @@ class ApiKeyDetailView(APIView):
"""
def delete(self, request, key_id):
- if not settings.DAYBOOK_KEY_PROVISIONING_API:
+ if not settings.AGENTFEED_KEY_PROVISIONING_API:
return Response({"detail": "Key provisioning API is disabled on this instance."}, status=403)
user = request.user if request.user.is_authenticated else resolve_token_user(request)
if user is None:
@@ -858,7 +858,7 @@ def create_binary_artifact(data, request, user):
# Allowlist for sanitizing the "themed" text kinds (markdown, deck,
-# html_fragment) — kinds where Daybook, not the poster, owns presentation, so
+# html_fragment) — kinds where AgentFeed, not the poster, owns presentation, so
# they must never be a script-execution vector (issue #014). html_full is the
# deliberate exception (verbatim, poster owns everything) and is NOT run
# through this. The list keeps ordinary formatting + tables + code-highlight
@@ -963,14 +963,14 @@ def wrap_json_viewer(content, title, request):
-
-
+
+
@@ -1007,18 +1007,18 @@ def wrap_csv_viewer(content, title, request):
-
-
+
+
@@ -1306,9 +1306,9 @@ def raw_artifact_path(artifact):
return None
-class DaybookLoginView(auth_views.LoginView):
+class AgentFeedLoginView(auth_views.LoginView):
template_name = "artifacts/login.html"
- authentication_form = DaybookLoginForm
+ authentication_form = AgentFeedLoginForm
def form_valid(self, form):
response = super().form_valid(form)
@@ -1459,7 +1459,7 @@ def raw_unavailable_html(artifact):
Raw source unavailable
- Daybook does not have preserved raw source for {html.escape(artifact.title)}.
+ AgentFeed does not have preserved raw source for {html.escape(artifact.title)}.