9.4 KiB
nextdav — Nextcloud DAV Helper
Summary
We created a new small skill project called nextdav to provide a reliable, low-level, Nextcloud-focused helper for direct DAV operations.
Location:
projects/nextdav/
This was created after the existing broader Nextcloud skill proved unreliable for some calendar event creation workflows, particularly around CalDAV event writes and verification.
Why we created it
The immediate trigger was trouble creating a calendar reminder for a vehicle registration renewal notice.
Observed problems with the existing Nextcloud/calendar path:
- bundled Nextcloud calendar create flow returned
HTTP 415 Unsupported Media Type - calendar listing/verification behaviour was not fully trustworthy for confirming newly created events
- successful-looking writes could not be assumed to be real until directly verified
That exposed a need for a smaller, more explicit fallback tool that:
- uses the standard Nextcloud env vars already available in OpenClaw Gateway installs
- talks directly to WebDAV / CalDAV
- returns clean JSON
- is easy to test, verify, and extend
- avoids relying on a larger abstraction layer when precise behaviour matters
Design decision
We deliberately did not rewrite the whole existing Nextcloud skill.
Instead, we created a compact companion skill that is:
- Nextcloud-focused
- direct DAV underneath
- intentionally small in scope
- suitable as a reliable fallback / explicit tool for critical operations
Phase 1 scope implemented
Calendar
Implemented:
calendar list-calendarscalendar create-eventcalendar get-eventcalendar delete-event
Files
Implemented:
files lsfiles uploadfiles downloadfiles deletefiles mkdir
Phase 2 scope implemented
Contacts
Implemented:
contacts list-addressbookscontacts listcontacts createcontacts getcontacts delete
Current limitation
contacts list --include-cardsworks, but can be heavy on large addressbooks because it fetches cards one-by-one for richer parsed output.- Likely future refinements:
search, lighter summary listing, and additional update/delete/search ergonomics.
Phase 3 scope implemented
Calendar date-range querying
Implemented:
calendar list-events --calendar ... --from ... --to ...- optional text filtering via
--query
Why this was added
We immediately ran into a practical need to ask for:
- today's calendar items
- tomorrow's calendar items
- date-range calendar checks
- keyword-like event searches within a date window
This made date-range event querying an obvious next step for nextdav rather than continuing with ad-hoc DAV queries.
Current behaviour
- returns structured JSON event summaries for a given calendar and date range
- supports optional case-insensitive filtering against summary/description
- effectively covers the likely “search-events --from --to” use case without needing a separate duplicate command yet
Known oddity
A malformed or unusual existing calendar object (Cathy Yoga in Tilba) still appears in some date-range results despite having an old-looking DTSTART/DTEND value.
This suggests at least one oddball event object exists in the calendar data, so future hardening may include additional filtering of obviously out-of-range artefacts after fetch.
Phase 4 scope implemented
Notes API sibling helper
Implemented a new sibling script:
projects/nextdav/scripts/nextnotes.py
This was chosen instead of forcing notes into nextdav.py because notes are not DAV-backed; they use the Nextcloud Notes API directly.
Implemented note commands
listlist --category ...search --query ...get --id ...createeditdelete
Notes API findings
- endpoint works on this instance:
/index.php/apps/notes/api/v1/notes - implicit auth-helper style was not enough
- explicit Basic Auth header was required
NEXTCLOUD_INSECURE=1is still needed for this self-signed setup- note objects return useful fields including
id,title,category,internalPath,etag, and fullcontent
Full lifecycle test passed
A temporary note was:
- created
- retrieved
- edited
- found via
search --query - deleted
- confirmed absent via
HTTP 404
Why this matters
This gives us a lightweight, token-efficient way to:
- list notes by category
- search notes by content/title/path
- retrieve and manipulate note content cleanly
It also keeps the architecture honest:
nextdav.py= DAV-backed calendar/files/contactsnextnotes.py= Notes API helper
Phase 5 skill promotion
Promoted into workspace skill directory
The helper has now been promoted from the development project into the production skill location:
workspace/skills/nextdav/
Copied payload only:
SKILL.mdscripts/references/
Left behind:
- git repo metadata and project-only baggage
Production wording polish
SKILL.md was tightened to clearly state:
nextdav.py= calendar, files, contactsnextnotes.py= notes- standard env vars required
NEXTCLOUD_INSECURE=1for self-signed setups- JSON-only / explicit-behaviour intent
Post-copy smoke result
- notes search from the new skill location worked
- calendar
list-eventsfrom the new skill location executed successfully but returned zero events for the shared Work calendar on a date where a newly-created event was expected
Current caveat under investigation
Production promotion succeeded, but calendar list-events still appears to have edge-case anomalies around shared-calendar range listing and/or event filtering. Create/get is proven; list-range behaviour needs further investigation.
Phase 6 timezone fix for list-events
Root cause confirmed
The date-range anomaly on the shared Work calendar was caused by date-only values (YYYY-MM-DD) being expanded as UTC day bounds instead of local calendar day bounds.
Example:
- local event: 2026-04-28 09:00 AEST
- stored as:
20260427T230000Z - old single-day query for
2026-04-28missed it because the query window started at2026-04-28T00:00:00Z
Fix applied
parse_range_datetime() now treats date-only values as local day boundaries using:
- default timezone:
Australia/Sydney - optional override:
NEXTDAV_TIMEZONE
It then converts those local boundaries to UTC for the CalDAV REPORT.
Validation
After the fix:
calendar list-events --calendar "Work (David Manning)" --from "2026-04-28" --to "2026-04-28"now correctly returns the created 9:00am event- Personal calendar sanity check also returned the expected
Cruiser pink slipevent for the 28th
Remaining separate caveat
The old Cathy Yoga in Tilba object still appears in some range results, indicating a separate malformed/unusual calendar object issue unrelated to the timezone fix.
Important environment lesson
The Nextcloud instance uses a self-signed certificate.
Because of that, Python HTTPS verification initially failed with:
CERTIFICATE_VERIFY_FAILED
We patched nextdav.py to support:
NEXTCLOUD_INSECURE=1
That now allows the helper to work against this specific Nextcloud setup, matching the practical reality already reflected in existing curl -k usage elsewhere.
Implementation files
Created:
projects/nextdav/SKILL.mdprojects/nextdav/scripts/nextdav.pyprojects/nextdav/references/design-notes.md
What we tested
Smoke tests passed
- calendar discovery works
- file listing works
- addressbook discovery works
Full lifecycle tests passed
Calendar
- created a temporary event in
Personal - read it back successfully
- deleted it successfully
- confirmed it no longer existed via
HTTP 404
Files
- uploaded a temporary test file to
/Soren/nextdav-smoke-test.txt - downloaded/read it back successfully
- deleted it successfully
- confirmed it no longer existed via
HTTP 404
Contacts
- created a temporary contact in
Contacts - retrieved it successfully with parsed vCard fields
- deleted it successfully
- confirmed it no longer existed via
HTTP 404
Parser cleanup
After initial testing, the iCalendar parser was cleaned up so that event output now only returns meaningful VEVENT fields rather than noisy ICS container keys.
This removed harmless but annoying output artefacts like:
BEGIN: VEVENTEND: VCALENDAR
Contact test hiccup and fix
During the first contact lifecycle test, the contact itself created successfully, but the ad-hoc verification/delete test snippets failed because they did not carry the self-signed TLS bypass logic.
Rather than relying on external one-off verification scripts, we fixed this properly by adding native commands:
contacts getcontacts delete
That made contacts lifecycle testing native to nextdav itself and cleaned up the testing process.
Why this matters going forward
nextdav now gives us a reliable base for:
- creating/verifying calendar reminders
- handling Nextcloud file uploads/downloads more explicitly
- managing contacts directly via CardDAV
- lower token overhead compared with re-explaining brittle workarounds each time
- more trustworthy behaviour when accuracy matters
Current usage pattern
Use:
NEXTCLOUD_INSECURE=1 python3 projects/nextdav/scripts/nextdav.py ...
Examples:
NEXTCLOUD_INSECURE=1 python3 projects/nextdav/scripts/nextdav.py calendar list-calendars
NEXTCLOUD_INSECURE=1 python3 projects/nextdav/scripts/nextdav.py files ls --path "/Soren"
NEXTCLOUD_INSECURE=1 python3 projects/nextdav/scripts/nextdav.py contacts list-addressbooks