Compare commits
No commits in common. "v0.1.1" and "v0.1.0" have entirely different histories.
9 changed files with 42 additions and 305 deletions
68
CHANGELOG.md
68
CHANGELOG.md
|
|
@ -1,68 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to Daybook 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__`.
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
_Nothing yet._
|
|
||||||
|
|
||||||
## [0.1.1] — 2026-09-03
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Deployment crash-loop: the vendored `gridjs.umd.js` referenced a
|
|
||||||
`gridjs.umd.js.map` sourcemap that isn't in the repo, so `collectstatic`
|
|
||||||
(strict manifest storage) failed on container start and v0.1.0 never came
|
|
||||||
up. Removed the dangling sourcemap trailer (debug-only, no runtime effect).
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- `GET /api/skill` serves the bundled agent-posting skill as Markdown (or
|
|
||||||
`?format=json`), with `DAYBOOK_URL` pre-filled to the instance's own
|
|
||||||
origin so the instructions are ready to hand to an AI. Advertised as
|
|
||||||
`skill_url` in `/api/capabilities`.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Rewrote the posting skill (`skills/daybook-posting/SKILL.md`) to be
|
|
||||||
location-agnostic: no longer assumes a local source checkout, reframes
|
|
||||||
`DAYBOOK_URL` as set-your-instance, and adds a raw `curl` fallback.
|
|
||||||
|
|
||||||
## [0.1.0] — 2026-09-03
|
|
||||||
|
|
||||||
First tagged release.
|
|
||||||
|
|
||||||
### Artifacts & rendering
|
|
||||||
- Artifact kinds: `markdown`, `html_fragment`, `html_full`, `deck`, `json`,
|
|
||||||
`csv`, `image`, `pdf`, `binary`.
|
|
||||||
- `.xlsx` uploads convert server-side to a `csv` artifact (active sheet,
|
|
||||||
saved formula results).
|
|
||||||
- Markdown code blocks get syntax highlighting; tables (markdown &
|
|
||||||
html_fragment) render styled — monospace cells, sans-serif headers.
|
|
||||||
- `json`/`csv` render as an interactive collapsible tree / sortable table
|
|
||||||
(Grid.js + renderjson, vendored locally).
|
|
||||||
- Unified off-white document background; system sans-serif body text.
|
|
||||||
|
|
||||||
### API
|
|
||||||
- `POST/GET/PATCH/DELETE /api/artifacts`, list filters (source/label/day/
|
|
||||||
kind/q), `/api/assets` image upload, `/api/capabilities` (reports
|
|
||||||
`version`). Per-user bearer-token ingest; session-authed reads.
|
|
||||||
- Server-side validation: per-kind content shape, field length limits,
|
|
||||||
string-typed source fields, valid-JSON for `json`.
|
|
||||||
|
|
||||||
### Sharing, quotas, security
|
|
||||||
- Per-artifact public share links (`/s/{token}`), owner-toggled, revocable.
|
|
||||||
- Per-user disk quotas with oldest-unkept auto-eviction and a Keep flag.
|
|
||||||
- Allowlist HTML sanitization (nh3) + `script-src 'none'` CSP for the themed
|
|
||||||
kinds; deep-JSON parse errors return 400 (not 500).
|
|
||||||
|
|
||||||
### UI
|
|
||||||
- Multi-user auth; feed with source sidebar + label filtering; settings area
|
|
||||||
(Account, Storage, Demo Data, Users, About); light/dark toggle; app icon.
|
|
||||||
|
|
||||||
### Deployment
|
|
||||||
- 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.1.1...HEAD
|
|
||||||
[0.1.1]: https://git.cygnus.systems/peter/daybook/compare/v0.1.0...v0.1.1
|
|
||||||
[0.1.0]: https://git.cygnus.systems/peter/daybook/releases/tag/v0.1.0
|
|
||||||
|
|
@ -14,7 +14,6 @@ urlpatterns = [
|
||||||
path("assets/<str:asset_id>", views.asset_view, name="asset"),
|
path("assets/<str:asset_id>", views.asset_view, name="asset"),
|
||||||
path("api/assets", views.AssetUploadView.as_view(), name="asset-upload-api"),
|
path("api/assets", views.AssetUploadView.as_view(), name="asset-upload-api"),
|
||||||
path("api/capabilities", views.CapabilitiesView.as_view(), name="capabilities-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", views.ArtifactListCreateView.as_view(), name="artifact-api"),
|
||||||
path("api/artifacts/<str:artifact_id>", views.ArtifactDetailView.as_view(), name="artifact-detail-api"),
|
path("api/artifacts/<str:artifact_id>", views.ArtifactDetailView.as_view(), name="artifact-detail-api"),
|
||||||
path("settings/", views.settings_home, name="settings-home"),
|
path("settings/", views.settings_home, name="settings-home"),
|
||||||
|
|
|
||||||
|
|
@ -322,57 +322,11 @@ class CapabilitiesView(APIView):
|
||||||
"/api/artifacts": ["GET", "POST"],
|
"/api/artifacts": ["GET", "POST"],
|
||||||
"/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
|
"/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
|
||||||
"/api/assets": ["POST"],
|
"/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
|
FEED_WINDOW_DAYS = 7
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Single source of truth for the app version. Bump this, commit, then tag the
|
# Single source of truth for the app version. Bump this, commit, then tag the
|
||||||
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
|
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
|
||||||
# GET /api/capabilities ("version").
|
# GET /api/capabilities ("version").
|
||||||
__version__ = "0.1.1"
|
__version__ = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -202,52 +202,30 @@ Confirm the response includes `id`, `url`, and `api_url`, and that opening
|
||||||
|
|
||||||
## 7. Redeploy Loop (routine updates)
|
## 7. Redeploy Loop (routine updates)
|
||||||
|
|
||||||
Releases are tagged `vX.Y.Z` (SemVer). Deploy a specific tag rather than a
|
This is the command you run every time you're asked to ship a new version:
|
||||||
moving branch, so what's live is always a known, named version — and note
|
|
||||||
the currently-deployed one before switching, for a clean rollback target:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/daybook
|
cd /opt/daybook
|
||||||
PREV_TAG=$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD) # what's live now
|
PREV_SHA=$(git rev-parse HEAD)
|
||||||
git fetch --tags
|
git pull
|
||||||
git checkout v0.1.0 # the tag you were told to deploy (latest: git tag -l | sort -V | tail -1)
|
|
||||||
docker compose -f deploy/docker-compose.yml up -d --build
|
docker compose -f deploy/docker-compose.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Then repeat the verification in step 6 (and confirm `/api/capabilities`
|
Then repeat the verification in step 6 (capabilities check at minimum). If it
|
||||||
reports the `version` you expect). If it fails, roll back to the previous
|
fails:
|
||||||
version and report the failure (build log / migration error / failed health
|
|
||||||
check) rather than retrying blindly:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout "$PREV_TAG"
|
git checkout "$PREV_SHA"
|
||||||
docker compose -f deploy/docker-compose.yml up -d --build
|
docker compose -f deploy/docker-compose.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
Caveat: rollback reverts code, not data — a release that already applied a
|
to roll back, then report the failure (build log / migration error / failed
|
||||||
DB migration may not cleanly downgrade against the migrated `db.sqlite3`.
|
health check) back to the operator rather than retrying blindly.
|
||||||
For a routine forward release that's fine; if a rollback is needed after a
|
|
||||||
migration, restore the pre-deploy `deploy/data` backup (see §9) alongside
|
|
||||||
the code checkout.
|
|
||||||
|
|
||||||
`deploy/.env` and the data directory (wherever `DAYBOOK_HOST_DATA_DIR` points,
|
`deploy/.env` and the data directory (wherever `DAYBOOK_HOST_DATA_DIR` points,
|
||||||
or `deploy/data` by default) are untracked, so `git pull` never touches them —
|
or `deploy/data` by default) are untracked, so `git pull` never touches them —
|
||||||
no need to back them up before a routine redeploy.
|
no need to back them up before a routine redeploy.
|
||||||
|
|
||||||
**If the container crash-loops right after a build** (`docker compose ps`
|
|
||||||
shows `restarting`, health never goes green), the most likely cause is the
|
|
||||||
entrypoint's `collectstatic` step failing — check `docker logs daybook`
|
|
||||||
before assuming an env/network problem. A `whitenoise ... MissingFileError`
|
|
||||||
there means a static asset references a file that isn't present (this is what
|
|
||||||
bit v0.1.0). No data or schema impact; fix the asset in the repo, tag, and
|
|
||||||
redeploy.
|
|
||||||
|
|
||||||
**If `git checkout <tag>` refuses because of local working-tree changes** to
|
|
||||||
tracked files (e.g. a previous on-server hotfix), reset those tracked files
|
|
||||||
first — `git checkout -- <file>` for a specific one, or `git stash` /
|
|
||||||
`git reset --hard` to clear all tracked drift. This never touches `deploy/.env`
|
|
||||||
or the data directory, which are untracked and therefore safe.
|
|
||||||
|
|
||||||
## 8. Hardening For Internet-Facing Deployments
|
## 8. Hardening For Internet-Facing Deployments
|
||||||
|
|
||||||
If this VM is reachable from the public internet (not just a private
|
If this VM is reachable from the public internet (not just a private
|
||||||
|
|
|
||||||
34
docs/api.md
34
docs/api.md
|
|
@ -529,11 +529,8 @@ Example response:
|
||||||
"methods": {
|
"methods": {
|
||||||
"/api/artifacts": ["GET", "POST"],
|
"/api/artifacts": ["GET", "POST"],
|
||||||
"/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
|
"/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
|
||||||
"/api/assets": ["POST"],
|
"/api/assets": ["POST"]
|
||||||
"/api/capabilities": ["GET"],
|
}
|
||||||
"/api/skill": ["GET"]
|
|
||||||
},
|
|
||||||
"skill_url": "http://127.0.0.1:8001/api/skill"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -548,33 +545,6 @@ endpoints, it accepts a bearer token as well as a browser session:
|
||||||
curl -H "Authorization: Bearer <your-personal-token>" http://127.0.0.1:8001/api/capabilities
|
curl -H "Authorization: Bearer <your-personal-token>" http://127.0.0.1:8001/api/capabilities
|
||||||
```
|
```
|
||||||
|
|
||||||
## Posting Skill
|
|
||||||
|
|
||||||
Endpoint:
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/skill
|
|
||||||
GET /api/skill?format=json
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns the bundled agent-posting skill (`skills/daybook-posting/SKILL.md`) —
|
|
||||||
the same instructions you'd give an AI so it knows how to publish to this
|
|
||||||
instance. The server rewrites the `DAYBOOK_URL` line to **this instance's own
|
|
||||||
origin**, so the text a caller receives already points at the right server;
|
|
||||||
a recipient only has to fill in their `DAYBOOK_TOKEN`.
|
|
||||||
|
|
||||||
Same auth as capabilities (a browser session **or** a bearer token), so a
|
|
||||||
logged-in user can copy it to hand to their agent, or an agent can fetch it
|
|
||||||
directly. The default response is raw Markdown (`text/markdown`); add
|
|
||||||
`?format=json` for `{"version", "url", "skill"}`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Fetch the ready-to-use instructions for this instance
|
|
||||||
curl -H "Authorization: Bearer <your-personal-token>" http://127.0.0.1:8001/api/skill
|
|
||||||
```
|
|
||||||
|
|
||||||
Discover it programmatically via `skill_url` in the capabilities response.
|
|
||||||
|
|
||||||
## Current MVP Constraints
|
## Current MVP Constraints
|
||||||
|
|
||||||
- Each user has a personal ingest API token; there is no shared token.
|
- Each user has a personal ingest API token; there is no shared token.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,26 @@
|
||||||
---
|
---
|
||||||
name: daybook-posting
|
name: daybook-posting
|
||||||
description: Use when you have produced a finished deliverable such as a report, summary, brief, diagram, generated HTML, PDF, deck, or status update that a human should review later outside this chat. Publishes the artifact to a Daybook instance and returns a URL.
|
description: Use when you have produced a finished deliverable such as a report, summary, brief, diagram, generated HTML, PDF, deck, or status update that a human should review later outside this chat. Publishes the artifact to Daybook and returns a URL.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Daybook Posting
|
# Daybook Posting
|
||||||
|
|
||||||
Daybook is a private artifact journal for completed agent outputs. It stores, indexes, and displays artifacts for human review. It does not execute commands, run workflows, or continue conversations.
|
Daybook is a private artifact journal for completed agent outputs. It stores, indexes, and displays artifacts for human review. It does not execute commands, run workflows, or continue conversations.
|
||||||
|
|
||||||
You post to a **running Daybook server**, which may be anywhere — a cloud VM, a container, or a workstation. You do **not** need a checkout of Daybook's source. The only things that tie you to a particular instance are two environment variables: `DAYBOOK_URL` (where the server is) and `DAYBOOK_TOKEN` (who you are). Set those and the rest of this skill works unchanged regardless of where you or the server run.
|
Always use the helper script. Do not hand-build curl, JSON, PowerShell multipart, or inline shell content.
|
||||||
|
|
||||||
|
From the project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python skills/daybook-posting/scripts/daybook_post.py --check
|
||||||
|
python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report" --content-file report.md
|
||||||
|
cat report.md | python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report"
|
||||||
|
python skills/daybook-posting/scripts/daybook_post.py --file diagram.svg --title "Architecture Diagram"
|
||||||
|
```
|
||||||
|
|
||||||
|
The project also keeps a compatibility wrapper at `scripts/daybook_post.py`, but the canonical helper is bundled inside this skill.
|
||||||
|
|
||||||
|
The artifact URL is printed as the final stdout line.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|
@ -17,54 +30,12 @@ config) — the helper reads `os.environ` directly. There is no `.env` file
|
||||||
support; a `.env` file in the working directory is not read automatically.
|
support; a `.env` file in the working directory is not read automatically.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
DAYBOOK_URL=https://your-daybook-instance.example.com # your server's base URL
|
DAYBOOK_URL=http://127.0.0.1:8001
|
||||||
DAYBOOK_TOKEN=your-personal-api-token # from Settings -> Account
|
DAYBOOK_TOKEN=your-personal-api-token
|
||||||
DAYBOOK_SOURCE=agent # a stable label for what's posting
|
DAYBOOK_SOURCE=agent
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`DAYBOOK_URL`** — the base URL of the Daybook instance you're posting to.
|
`DAYBOOK_TOKEN` is your personal per-user API token, from Settings → Account in the app (not a token shared by everyone). Artifacts posted with your token land only in your feed.
|
||||||
Set it to your server. If it's unset, the helper falls back to
|
|
||||||
`http://127.0.0.1:8001`, which is only correct when Daybook is running
|
|
||||||
locally on the same machine (its development default). For any remote or
|
|
||||||
deployed instance you **must** set this.
|
|
||||||
- **`DAYBOOK_TOKEN`** — your personal per-user API token, from Settings →
|
|
||||||
Account in the app (not a token shared by everyone). Artifacts posted with
|
|
||||||
your token land only in your feed.
|
|
||||||
- **`DAYBOOK_SOURCE`** — a free-text label for whatever is posting
|
|
||||||
(agent-profile, script, or person name). Optional; can also be passed per
|
|
||||||
call with `--source`.
|
|
||||||
|
|
||||||
## The Helper
|
|
||||||
|
|
||||||
Always use the bundled helper script. Do not hand-build curl, JSON,
|
|
||||||
PowerShell multipart, or inline shell content (a `curl` fallback for
|
|
||||||
environments without the helper is documented at the end — the script is
|
|
||||||
still preferred everywhere it's available).
|
|
||||||
|
|
||||||
The helper lives at **`scripts/daybook_post.py` inside this skill's own
|
|
||||||
directory** — not in any project you happen to be working in. Invoke it by
|
|
||||||
its absolute path. For brevity, the examples below assume you've captured
|
|
||||||
that path once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Point this at the helper inside THIS skill directory (absolute path).
|
|
||||||
DAYBOOK_POST=/abs/path/to/this/skill/scripts/daybook_post.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python "$DAYBOOK_POST" --check
|
|
||||||
python "$DAYBOOK_POST" --kind markdown --title "Build Report" --content-file report.md
|
|
||||||
cat report.md | python "$DAYBOOK_POST" --kind markdown --title "Build Report"
|
|
||||||
python "$DAYBOOK_POST" --file diagram.svg --title "Architecture Diagram"
|
|
||||||
```
|
|
||||||
|
|
||||||
The artifact URL is printed as the final stdout line.
|
|
||||||
|
|
||||||
(If you *are* working inside a Daybook source checkout, a compatibility
|
|
||||||
wrapper also exists at `scripts/daybook_post.py` in the repo root, but the
|
|
||||||
canonical helper is the one bundled in this skill.)
|
|
||||||
|
|
||||||
## Kind Cheat Sheet
|
## Kind Cheat Sheet
|
||||||
|
|
||||||
|
|
@ -94,9 +65,8 @@ presentation.
|
||||||
|
|
||||||
This script only **creates** artifacts — none of the following are
|
This script only **creates** artifacts — none of the following are
|
||||||
possible through it. Updating an existing artifact's title/tags/metadata,
|
possible through it. Updating an existing artifact's title/tags/metadata,
|
||||||
or deleting one, requires the raw API (see the instance's `docs/api.md` or
|
or deleting one, requires the raw API (see `docs/api.md`); enabling its
|
||||||
`GET /api/capabilities`); enabling its public share link is feed-UI-only,
|
public share link is feed-UI-only, with no API equivalent at all yet.
|
||||||
with no API equivalent at all yet.
|
|
||||||
|
|
||||||
## CDN Snippets For `html_full`
|
## CDN Snippets For `html_full`
|
||||||
|
|
||||||
|
|
@ -149,12 +119,11 @@ artifact), upload it first and reference the returned URL:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Upload -> prints the URL as the last line
|
# 1. Upload -> prints the URL as the last line
|
||||||
python "$DAYBOOK_POST" --upload-image chart.png
|
python skills/daybook-posting/scripts/daybook_post.py --upload-image chart.png
|
||||||
|
|
||||||
# 2. Reference that URL in your markdown, then post as usual.
|
# 2. Reference that URL in your markdown, then post as usual
|
||||||
# The printed URL is absolute against your DAYBOOK_URL, e.g.
|
# 
|
||||||
# 
|
python skills/daybook-posting/scripts/daybook_post.py report.md
|
||||||
python "$DAYBOOK_POST" report.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The image is linked to the artifact that references it and is deleted along with it.
|
The image is linked to the artifact that references it and is deleted along with it.
|
||||||
|
|
@ -167,19 +136,19 @@ just post the file positionally; auto-mode detects `.csv`/`.json` and picks
|
||||||
the right kind automatically:
|
the right kind automatically:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python "$DAYBOOK_POST" results.csv
|
python skills/daybook-posting/scripts/daybook_post.py results.csv
|
||||||
python "$DAYBOOK_POST" --kind json --title "Run Summary" --content-file summary.json
|
python skills/daybook-posting/scripts/daybook_post.py --kind json --title "Run Summary" --content-file summary.json
|
||||||
```
|
```
|
||||||
|
|
||||||
`.xlsx` spreadsheets work too, with no special flag needed — just `--file`
|
`.xlsx` spreadsheets work too, with no special flag needed — just `--file`
|
||||||
(or post it positionally, same as any other file):
|
(or post it positionally, same as any other file):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python "$DAYBOOK_POST" balance_sheet.xlsx
|
python skills/daybook-posting/scripts/daybook_post.py balance_sheet.xlsx
|
||||||
```
|
```
|
||||||
|
|
||||||
The server converts it to a `csv` artifact automatically. Known
|
The server converts it to a `csv` artifact automatically. Known
|
||||||
limitations (see the instance's `docs/api.md` for the full list):
|
limitations (see `docs/api.md` for the full list):
|
||||||
|
|
||||||
- Only the workbook's **active sheet** converts; other sheets are dropped.
|
- Only the workbook's **active sheet** converts; other sheets are dropped.
|
||||||
- Formula cells need to have been calculated and saved by real spreadsheet
|
- Formula cells need to have been calculated and saved by real spreadsheet
|
||||||
|
|
@ -196,7 +165,7 @@ as a binary, use `--file path --as-markdown` to wrap it in a Markdown
|
||||||
fenced code block (still gets syntax highlighting):
|
fenced code block (still gets syntax highlighting):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python "$DAYBOOK_POST" --file script.py --as-markdown --title "Generated Script"
|
python skills/daybook-posting/scripts/daybook_post.py --file script.py --as-markdown --title "Generated Script"
|
||||||
```
|
```
|
||||||
|
|
||||||
Without `--as-markdown`, it uploads as a downloadable binary file instead.
|
Without `--as-markdown`, it uploads as a downloadable binary file instead.
|
||||||
|
|
@ -220,37 +189,8 @@ context like `run_id` or `model` — these end up in the artifact's
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- Run `--check` first if unsure Daybook is reachable.
|
- Run `--check` first if unsure Daybook is reachable.
|
||||||
- Make sure `DAYBOOK_URL` points at the right instance and `DAYBOOK_TOKEN` is set before posting anything real.
|
|
||||||
- Use a stable `DAYBOOK_SOURCE` or pass `--source` (the agent-profile, script, or person name that produced this artifact).
|
- Use a stable `DAYBOOK_SOURCE` or pass `--source` (the agent-profile, script, or person name that produced this artifact).
|
||||||
- Use `--tags a,b,c` for filtering.
|
- Use `--tags a,b,c` for filtering.
|
||||||
- Use `--meta run_id=... --meta model=...` for machine-readable context.
|
- Use `--meta run_id=... --meta model=...` for machine-readable context.
|
||||||
- Never inline multiline content into the shell command; use a file or stdin.
|
- Never inline multiline content into the shell command; use a file or stdin.
|
||||||
- Add `--dry-run` to see exactly what would be sent (method, headers, payload) without actually posting — useful when unsure a payload is well-formed.
|
- Add `--dry-run` to see exactly what would be sent (method, headers, payload) without actually posting — useful when unsure a payload is well-formed.
|
||||||
|
|
||||||
## `curl` Fallback (No Helper Available)
|
|
||||||
|
|
||||||
Prefer the helper above — it handles multipart, content-type inference,
|
|
||||||
JSON shaping, and provenance flags for you. Use raw `curl` **only** when you
|
|
||||||
genuinely can't run the bundled Python script (e.g. a minimal environment
|
|
||||||
with no Python). Text artifacts post as JSON to `POST /api/artifacts` with
|
|
||||||
bearer-token auth:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS -X POST "$DAYBOOK_URL/api/artifacts" \
|
|
||||||
-H "Authorization: Bearer $DAYBOOK_TOKEN" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"kind":"markdown","title":"Build Report","source":"agent","content":"# Hello\n\nBody text."}'
|
|
||||||
```
|
|
||||||
|
|
||||||
File/image artifacts use multipart instead:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS -X POST "$DAYBOOK_URL/api/artifacts" \
|
|
||||||
-H "Authorization: Bearer $DAYBOOK_TOKEN" \
|
|
||||||
-F "kind=image" -F "title=Architecture Diagram" -F "source=agent" \
|
|
||||||
-F "file=@diagram.png"
|
|
||||||
```
|
|
||||||
|
|
||||||
`GET $DAYBOOK_URL/api/capabilities` (no auth needed beyond a login/token per
|
|
||||||
that instance's config) lists supported kinds, methods, and the server
|
|
||||||
version if you need to confirm the contract before posting.
|
|
||||||
|
|
|
||||||
1
static/vendor/gridjs/gridjs.umd.js
vendored
1
static/vendor/gridjs/gridjs.umd.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -12,7 +12,6 @@ from django.utils import timezone
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from daybook_project import __version__
|
|
||||||
from artifacts.models import Artifact
|
from artifacts.models import Artifact
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
@ -79,42 +78,6 @@ class ArtifactAPITestCase(APITestCase):
|
||||||
response = self.client.get(reverse("capabilities-api"))
|
response = self.client.get(reverse("capabilities-api"))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_capabilities_advertises_skill(self):
|
|
||||||
response = self.client.get(reverse("capabilities-api"), **self.auth_headers(self.token))
|
|
||||||
self.assertEqual(response.data["methods"]["/api/skill"], ["GET"])
|
|
||||||
self.assertTrue(response.data["skill_url"].endswith("/api/skill"))
|
|
||||||
|
|
||||||
# -- skill ----------------------------------------------------------
|
|
||||||
|
|
||||||
def test_skill_requires_auth(self):
|
|
||||||
response = self.client.get(reverse("skill-api"))
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_skill_returns_markdown_with_origin_filled_in(self):
|
|
||||||
response = self.client.get(reverse("skill-api"), **self.auth_headers(self.token))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response["Content-Type"], "text/markdown; charset=utf-8")
|
|
||||||
body = response.content.decode()
|
|
||||||
# Frontmatter is intact and the config placeholder is rewritten to this
|
|
||||||
# instance's origin (http://testserver under the test client).
|
|
||||||
self.assertIn("name: daybook-posting", body)
|
|
||||||
self.assertIn("DAYBOOK_URL=http://testserver", body)
|
|
||||||
self.assertNotIn("your-daybook-instance.example.com", body)
|
|
||||||
|
|
||||||
def test_skill_json_format(self):
|
|
||||||
response = self.client.get(
|
|
||||||
reverse("skill-api"), {"format": "json"}, **self.auth_headers(self.token)
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.data["version"], __version__)
|
|
||||||
self.assertEqual(response.data["url"], "http://testserver")
|
|
||||||
self.assertIn("name: daybook-posting", response.data["skill"])
|
|
||||||
|
|
||||||
def test_skill_with_session(self):
|
|
||||||
self.client.force_login(self.user)
|
|
||||||
response = self.client.get(reverse("skill-api"))
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# -- create (POST) --------------------------------------------------
|
# -- create (POST) --------------------------------------------------
|
||||||
|
|
||||||
def test_create_markdown_artifact(self):
|
def test_create_markdown_artifact(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue