Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reliable Nextcloud integration for Notes, Tasks, Calendar, Files, and Contacts (CardDAV-safe vCard handling with robust contact create/update).
.claude/skills/leoyeai-nextcloud-aio-oc-nextcloud-aio-openclaw/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 204% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 326% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 135% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 294% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 300% | 0% |
This skill connects OpenClaw to Nextcloud for:
Primary goal: Predictable, loss-resistant Nextcloud reads/writes.
This skill is validated against:
32.0.6)2026.3.2)Set these environment variables:
NEXTCLOUD_URL (example: https://cloud.example.com)NEXTCLOUD_USERNEXTCLOUD_TOKEN (App Password strongly preferred)NEXTCLOUD_TIMEZONE (recommended, IANA timezone like America/Los_Angeles; used for timezone-less date/time inputs)This section is the source of truth for registry metadata parity.
node (Node.js 20+)NEXTCLOUD_URL, NEXTCLOUD_USER, NEXTCLOUD_TOKENNEXTCLOUD_TIMEZONENEXTCLOUD_URL (plus normal redirects from that host)If a registry entry says "no required binaries" or "no required env vars", treat it as stale metadata and fix before publishing.
Before first execution in any new environment:
SKILL.md, scripts, examples, commit messages, or changelog text..env files out of git (see .gitignore).The skill ships two scripts: scripts/nextcloud.js (Node.js, text operations) and scripts/files_binary.py (Python 3, binary file transfers). Review both before running in new environments:
node --version (must be 20+)rg -n "https?://" scripts/nextcloud.jsrg -n "child_process|spawn\\(|exec\\(|require\\(\"fs\"\\)|require\\('fs'\\)|fs\\." scripts/nextcloud.jsrg -n "NEXTCLOUD_URL|NEXTCLOUD_USER|NEXTCLOUD_TOKEN|NEXTCLOUD_TIMEZONE" scripts/nextcloud.jsIf review confidence is low, do not run until code is reviewed by a trusted maintainer.
Quick grep-style checks on scripts/nextcloud.js should currently show:
process.env.NEXTCLOUD_URL|USER|TOKEN|TIMEZONE.${CONFIG.url}${endpoint} and uses fetch(...).child_process or fs usage in the bundle entry path.Quick checks on scripts/files_binary.py:
os.environ.get("NEXTCLOUD_URL|USER|TOKEN").{url}/remote.php/dav/files/{user}/{path}.urllib, base64, xml.etree); no third-party deps.This is not a full security audit. Re-run checks after every bundle update.
bashnode scripts/nextcloud.js <command> <subcommand> [options]
notes listnotes get --id <id>notes create --title <title> --content <content> [--category <category>]notes edit --id <id> [--title <title>] [--content <content>] [--category <category>]notes delete --id <id>tasks list [--calendar <calendar-name>]tasks create --title <title> [--calendar <calendar-name>] [--due <iso>] [--priority <0-9>] [--description <text>] [--timezone <IANA>]tasks edit --uid <uid> [--calendar <calendar-name>] [--title <title>] [--due <iso>] [--priority <0-9>] [--description <text>] [--timezone <IANA>]tasks delete --uid <uid> [--calendar <calendar-name>]tasks complete --uid <uid> [--calendar <calendar-name>]calendar list [--from <iso>] [--to <iso>] (default: next 7 days)calendar create --summary <summary> --start <iso-or-date> --end <iso-or-date> [--calendar <calendar-name>] [--description <text>] [--timezone <IANA>] [--all-day]calendar edit --uid <uid> [--calendar <calendar-name>] [--summary <summary>] [--start <iso-or-date>] [--end <iso-or-date>] [--description <text>] [--timezone <IANA>] [--all-day]calendar delete --uid <uid> [--calendar <calendar-name>]Important:
Contact birthdays appears as an events calendar but is read-only.contacts create/edit --birthday ..., not through calendar create/edit.calendar list returns event summary, start, end, and may include location and description when present.calendars list [--type <tasks|events>]files list [--path <path>]files search --query <query> (supports natural-language input; ranks by filename/path token relevance)files get --path <path> (accepts relative user path or full DAV href)files upload --path <path> --content <content>files delete --path <path>nextcloud.js passes file content as text strings, which corrupts binary formats. Use the companion Python script for any binary file:
bashpython3 scripts/files_binary.py download <nc_path> <local_path> python3 scripts/files_binary.py upload <local_path> <nc_path> python3 scripts/files_binary.py exists <nc_path> python3 scripts/files_binary.py list [<nc_path>]
Reads the same NEXTCLOUD_URL, NEXTCLOUD_USER, NEXTCLOUD_TOKEN env vars. Auto-detects MIME type from file extension (ODT, DOCX, XLSX, PDF, PNG, JPG, and more).
When to use which:
| Situation | Command | |-----------|---------| | Plain text files (.md, .txt, .csv) | node scripts/nextcloud.js files get/upload | | Binary files (.odt, .docx, .pdf, images) | python3 scripts/files_binary.py download/upload |
contacts list [--addressbook <name>]contacts get --uid <uid> [--addressbook <name>]contacts search --query <query> [--addressbook <name>]contacts create [--name <full-name>] [--first-name <given>] [--last-name <family>] [--middle-name <middle>] [--prefix <prefix>] [--suffix <suffix>] [--addressbook <name>] [--email <single>] [--emails <csv>] [--phone <single>] [--phones <csv>] [--organization <org>] [--title <title>] [--note <note>] [--birthday <YYYY-MM-DD|YYYYMMDD|--MM-DD|--MMDD>]contacts edit --uid <uid> [--addressbook <name>] [--name <full-name>] [--first-name <given>] [--last-name <family>] [--middle-name <middle>] [--prefix <prefix>] [--suffix <suffix>] [--email <single>] [--emails <csv>] [--phone <single>] [--phones <csv>] [--organization <org>] [--title <title>] [--note <note>] [--birthday <YYYY-MM-DD|YYYYMMDD|--MM-DD|--MMDD>]contacts delete --uid <uid> [--addressbook <name>]addressbooks listUse fullName and structuredName as canonical name fields.
Contact output includes:
uidaddressBookfullNamestructuredName with:familyNamegivenNameadditionalNameshonorificPrefixeshonorificSuffixesnameRaw (raw N value for diagnostics only; do not display to users by default)emails array or nullphones array or nullorganization, title, notebirthday normalized for readability (YYYY-MM-DD or --MM-DD when possible)birthdayRaw (raw CardDAV value)Apply these on every write to prevent corruption and confusion:
1) Identity and naming
UID from URL; always use payload uid.fullName and structuredName for user-facing names; keep nameRaw diagnostic-only.2) Contact and birthday integrity
YYYY-MM-DD, YYYYMMDD, --MM-DD, --MMDD.BDAY.contacts create/edit --birthday ...; never edit birthday-derived calendar entries directly.3) Intent-preserving updates
--addressbook; otherwise use remembered default or ask once.4) Calendar and task validation
end > start for timed events.SUMMARY, DESCRIPTION).0..9).--timezone or NEXTCLOUD_TIMEZONE.VALUE=DATE semantics with exclusive DTEND.5) Capability and safety checks
Contact birthdays as read-only/system calendar.VEVENT + VTODO); keep them eligible for both event/task flows.6) Files and post-write verification
files upload/get/delete.create, edit, complete, delete), verify with follow-up read/list when practical.0.5s, 1s, 2s before declaring failure.3.0 vs 4.0) and content on write/read.FN and N should each appear once in valid contacts.BDAY failures are often malformed input (for example stray delimiters), not server instability.HTTP 403 on calendar create/edit/delete -> likely read-only/system calendar or missing permission; switch to writable events calendar.HTTP 501 on files search -> WebDAV SEARCH unsupported; fallback to recursive PROPFIND + client-side filter.HTTP 415 on task/event update -> malformed ICS payload; normalize formatting and retry once.When user creates tasks/events without explicit calendar:
calendars list --type tasks or calendars list --type events.events, filter out likely read-only calendars first (Contact birthdays, Holidays, names containing read only or readonly).Memory keys:
default_task_calendardefault_event_calendarWhen user creates contacts without explicit address book:
addressbooks list.Memory key:
default_addressbookThese playbooks are execution shortcuts. Reliability and safety rules above still apply.
--addressbook or remembered default).--name for full-name input; use structured-name flags for split-name input.contacts create/edit --birthday ...), never calendar mutation commands.contacts get --uid ... when confidence is important.0..9) before write.Contact birthdays and system calendars as read-only.start/end and require end > start for timed events.--timezone or use NEXTCLOUD_TIMEZONE.--all-day with date values (not timed midnight ranges).All-day end-date normalization behavior:
start=YYYY-MM-DD, end=YYYY-MM-DD -> inclusive single-day input, normalized.start=YYYY-MM-DD, end=next-day -> already-exclusive single-day input.DTEND.Use this when user asks for appointment details, address, or "what is my appointment today":
NEXTCLOUD_TIMEZONE (or ask if timezone is unclear).calendar list --from <start-of-day-iso> --to <end-of-day-iso>.dermatology, doctor, provider name).summary, time window, location, and key lines from description.files get/upload/delete accept either a relative user path (for example /Share-Family/file.docx) or a full DAV href returned by search.python3 scripts/files_binary.py — never pass binary content through nextcloud.js files upload.files search with user phrasing directly; it auto-normalizes query text and ranks likely matches.When validating this skill in a live environment:
Use a unique timestamped suffix on all test data and clean up all temporary artifacts unless the user requests keeping them. Use least-privilege credentials for tests and monitor outbound traffic; the process should only communicate with NEXTCLOUD_URL.
Calendar exception handling:
calendar list.Persist these high-value rules in memory/rules:
contacts create/edit --birthday ...; never mutate Contact birthdays directly.calendar create/edit --all-day with date values (avoid timed midnight ranges).--timezone or NEXTCLOUD_TIMEZONE; ask if timezone is unknown.403, report likely read-only/permission issue.VALUE=DATE); avoid duplicate compatibility events unless explicitly requested.contacts get --uid ... immediately.contacts get and contacts search disagree briefly:calendars list --type events and verify at least one writable non-system calendar exists.All commands return JSON:
Success:
json{ "status": "success", "data": {} }
Error:
json{ "status": "error", "message": "Error description" }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 5,862 | 5,738 | -2% | 1 | 1 | 0% | 406 | 4,732 | +1066% | 0 | 0 | — |
case-16 | fail→fail | 4,389 | 4,394 | +0% | 1 | 1 | 0% | 547 | 4,715 | +762% | 0 | 0 | — |
case-02 | fail→fail | 10,785 | 6,169 | -43% | 1 | 1 | 0% | 2,109 | 4,843 | +130% | 0 | 0 | — |
case-03 | fail→fail | 5,685 | 4,882 | -14% | 1 | 1 | 0% | 372 | 4,753 | +1178% | 0 | 0 | — |
case-04 | fail→pass | 10,232 | 3,701 | -64% | 1 | 1 | 0% | 1,671 | 5,087 | +204% | 0 | 0 | — |
case-05 | fail→pass | 6,136 | 3,070 | -50% | 1 | 1 | 0% | 1,197 | 5,105 | +326% | 0 | 0 | — |
case-06 | fail→pass | 11,893 | 2,608 | -78% | 1 | 1 | 0% | 2,083 | 4,895 | +135% | 0 | 0 | — |
case-07 | pass→pass | 1,835 | 2,051 | +12% | 1 | 1 | 0% | 315 | 4,830 | +1433% | 0 | 0 | — |
case-08 | fail→pass | 7,508 | 2,041 | -73% | 1 | 1 | 0% | 1,225 | 4,827 | +294% | 0 | 0 | — |
case-09 | fail→pass | 7,078 | 1,841 | -74% | 1 | 1 | 0% | 1,184 | 4,731 | +300% | 0 | 0 | — |
case-10 | fail→fail | 4,638 | 4,251 | -8% | 1 | 1 | 0% | 657 | 4,643 | +607% | 0 | 0 | — |
case-11 | fail→pass | 8,591 | 5,346 | -38% | 1 | 1 | 0% | 1,556 | 5,337 | +243% | 0 | 0 | — |
case-12 | fail→pass | 6,212 | 1,931 | -69% | 1 | 1 | 0% | 1,006 | 4,762 | +373% | 0 | 0 | — |
case-13 | fail→pass | 6,572 | 10,572 | +61% | 1 | 1 | 0% | 1,228 | 5,660 | +361% | 0 | 0 | — |
case-14 | fail→fail | 6,483 | 4,252 | -34% | 1 | 1 | 0% | 974 | 4,590 | +371% | 0 | 0 | — |
case-15 | fail→pass | 1,626 | 1,030 | -37% | 1 | 1 | 0% | 190 | 4,593 | +2317% | 0 | 0 | — |
case-17 | pass→pass | 2,969 | 3,686 | +24% | 1 | 1 | 0% | 298 | 4,652 | +1461% | 0 | 0 | — |
case-18 | fail→fail | 5,986 | 6,128 | +2% | 1 | 1 | 0% | 1,017 | 4,776 | +370% | 0 | 0 | — |
case-19 | fail→fail | 11,669 | 4,892 | -58% | 1 | 1 | 0% | 2,307 | 4,735 | +105% | 0 | 0 | — |
case-20 | pass→pass | 14,138 | 3,677 | -74% | 1 | 1 | 0% | 2,308 | 5,139 | +123% | 0 | 0 | — |
case-21 | fail→pass | 6,895 | 4,470 | -35% | 1 | 1 | 0% | 1,264 | 5,237 | +314% | 0 | 0 | — |
case-22 | fail→fail | 11,449 | 6,637 | -42% | 1 | 1 | 0% | 2,242 | 4,759 | +112% | 0 | 0 | — |
case-23 | fail→fail | 9,320 | 6,621 | -29% | 1 | 1 | 0% | 1,683 | 5,552 | +230% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 14 counted toward the lift figure. The other 9 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +43 percentage points is the difference between those two pass rates over the 14 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.