Files
Knowledge/chat-topics/nextdav-nextcloud-dav-helper.md
2026-05-05 09:40:28 +10:00

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-calendars
  • calendar create-event
  • calendar get-event
  • calendar delete-event

Files

Implemented:

  • files ls
  • files upload
  • files download
  • files delete
  • files mkdir

Phase 2 scope implemented

Contacts

Implemented:

  • contacts list-addressbooks
  • contacts list
  • contacts create
  • contacts get
  • contacts delete

Current limitation

  • contacts list --include-cards works, 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

  • list
  • list --category ...
  • search --query ...
  • get --id ...
  • create
  • edit
  • delete

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=1 is still needed for this self-signed setup
  • note objects return useful fields including id, title, category, internalPath, etag, and full content

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/contacts
  • nextnotes.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.md
  • scripts/
  • references/

Left behind:

  • git repo metadata and project-only baggage

Production wording polish

SKILL.md was tightened to clearly state:

  • nextdav.py = calendar, files, contacts
  • nextnotes.py = notes
  • standard env vars required
  • NEXTCLOUD_INSECURE=1 for self-signed setups
  • JSON-only / explicit-behaviour intent

Post-copy smoke result

  • notes search from the new skill location worked
  • calendar list-events from 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-28 missed it because the query window started at 2026-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 slip event 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.md
  • projects/nextdav/scripts/nextdav.py
  • projects/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: VEVENT
  • END: 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 get
  • contacts 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