Initial commit
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user