Add GET /api/skill endpoint serving the posting skill
Serves skills/daybook-posting/SKILL.md so users/agents can fetch the
posting instructions from the running instance instead of hunting for the
file. The server rewrites the DAYBOOK_URL config line to the request's own
origin, so the text handed to an agent already points at the right server;
the recipient only supplies their token.
- Same auth as /api/capabilities (session or bearer token); no user data.
- Raw text/markdown by default, ?format=json returns {version, url, skill}.
- Advertised via skill_url + a /api/skill methods entry in capabilities.
- Ships in the Docker image (COPY . /app, skills/ not dockerignored).
- Tests for auth, origin rewrite, JSON mode, session auth, capabilities ad.
- docs/api.md + CHANGELOG updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
caeb2bedc7
commit
fb69261218
5 changed files with 126 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ urlpatterns = [
|
|||
path("assets/<str:asset_id>", views.asset_view, name="asset"),
|
||||
path("api/assets", views.AssetUploadView.as_view(), name="asset-upload-api"),
|
||||
path("api/capabilities", views.CapabilitiesView.as_view(), name="capabilities-api"),
|
||||
path("api/skill", views.SkillView.as_view(), name="skill-api"),
|
||||
path("api/artifacts", views.ArtifactListCreateView.as_view(), name="artifact-api"),
|
||||
path("api/artifacts/<str:artifact_id>", views.ArtifactDetailView.as_view(), name="artifact-detail-api"),
|
||||
path("settings/", views.settings_home, name="settings-home"),
|
||||
|
|
|
|||
|
|
@ -322,11 +322,57 @@ class CapabilitiesView(APIView):
|
|||
"/api/artifacts": ["GET", "POST"],
|
||||
"/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
|
||||
"/api/assets": ["POST"],
|
||||
"/api/capabilities": ["GET"],
|
||||
"/api/skill": ["GET"],
|
||||
},
|
||||
"skill_url": request.build_absolute_uri(reverse("skill-api")),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
SKILL_MD_PATH = settings.BASE_DIR / "skills" / "daybook-posting" / "SKILL.md"
|
||||
|
||||
|
||||
def render_posting_skill(origin):
|
||||
"""Return the bundled posting SKILL.md with its DAYBOOK_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."""
|
||||
text = SKILL_MD_PATH.read_text(encoding="utf-8")
|
||||
return re.sub(
|
||||
r"(?m)^(DAYBOOK_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
|
||||
right server. Same auth as capabilities: a logged-in user can copy it, or
|
||||
an agent can fetch it with its API token.
|
||||
|
||||
GET /api/skill -> raw Markdown (text/markdown)
|
||||
GET /api/skill?format=json -> {"version", "url", "skill"}
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
if not request.user.is_authenticated and resolve_token_user(request) is None:
|
||||
return HttpResponseForbidden("Login or a valid API token is required")
|
||||
|
||||
origin = request.build_absolute_uri("/").rstrip("/")
|
||||
try:
|
||||
skill = render_posting_skill(origin)
|
||||
except FileNotFoundError:
|
||||
raise Http404("Posting skill is not bundled with this deployment")
|
||||
|
||||
if request.GET.get("format") == "json":
|
||||
from daybook_project import __version__
|
||||
|
||||
return Response({"version": __version__, "url": origin, "skill": skill})
|
||||
return HttpResponse(skill, content_type="text/markdown; charset=utf-8")
|
||||
|
||||
|
||||
FEED_WINDOW_DAYS = 7
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue