262 lines
9.4 KiB
Markdown
262 lines
9.4 KiB
Markdown
# 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:
|
|
```bash
|
|
NEXTCLOUD_INSECURE=1 python3 projects/nextdav/scripts/nextdav.py ...
|
|
```
|
|
|
|
Examples:
|
|
```bash
|
|
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
|
|
```
|