# 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
```
