v0.3.0: rename app Daybook -> AgentFeed

Removes the "Daybook" name from everything user- and agent-facing, since no
agents are configured yet and this is the cheap moment to do it.

- UI brand, docs, README, CLAUDE.md -> AgentFeed.
- Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/
  KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged).
- Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py
  -> agentfeed_post.py (+ scripts/ wrapper and its test).
- static/daybook.css -> static/agentfeed.css; localStorage daybook.* ->
  agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart
  boundaries; json/csv viewer element ids; daybook_exception_handler ->
  agentfeed_exception_handler; compose service/container -> agentfeed.
- Key-provisioning API now OFF by default (independent of DEBUG); enable with
  AGENTFEED_KEY_PROVISIONING_API=1 on test installs only.
- Posting helper now points at Settings -> API Keys.

Deferred on purpose (a later, separate step): the Django package
`daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name.
The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched.

Full suite: 129 passing; live server verified serving under the new name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-04 13:10:28 +01:00
parent 4cacd8f64e
commit d30e2436ab
39 changed files with 303 additions and 284 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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):
</style>
</head>
<body class="artifact">
<script type="application/json" id="daybook-data">{safe_json}</script>
<div id="daybook-json-view"></div>
<script type="application/json" id="agentfeed-data">{safe_json}</script>
<div id="agentfeed-json-view"></div>
<script>
(function () {{
var raw = document.getElementById("daybook-data").textContent;
var raw = document.getElementById("agentfeed-data").textContent;
var data = JSON.parse(raw);
renderjson.set_show_to_level(2);
document.getElementById("daybook-json-view").appendChild(renderjson(data));
document.getElementById("agentfeed-json-view").appendChild(renderjson(data));
}})();
</script>
</body>
@ -1007,18 +1007,18 @@ def wrap_csv_viewer(content, title, request):
</style>
</head>
<body class="artifact">
<script type="application/json" id="daybook-data">{table_json}</script>
<div id="daybook-csv-view"></div>
<script type="application/json" id="agentfeed-data">{table_json}</script>
<div id="agentfeed-csv-view"></div>
<script>
(function () {{
var table = JSON.parse(document.getElementById("daybook-data").textContent);
var table = JSON.parse(document.getElementById("agentfeed-data").textContent);
new gridjs.Grid({{
columns: table.columns,
data: table.data,
search: true,
sort: true,
pagination: {{ limit: 20 }},
}}).render(document.getElementById("daybook-csv-view"));
}}).render(document.getElementById("agentfeed-csv-view"));
}})();
</script>
</body>
@ -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):
<body>
<main>
<h1>Raw source unavailable</h1>
<p>Daybook does not have preserved raw source for <strong>{html.escape(artifact.title)}</strong>.</p>
<p>AgentFeed does not have preserved raw source for <strong>{html.escape(artifact.title)}</strong>.</p>
</main>
</body>
</html>