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:
parent
4cacd8f64e
commit
d30e2436ab
39 changed files with 303 additions and 284 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue