Communitygithub.com

OpenLinkSoftware/ai-agent-skills

Publish a static HTML/CSS/JS website (generated by an RDF-Turtle + SHACL + Jinja2 pipeline, e.g. sites/openlink/rdf/build.py) to a Virtuoso WebDAV root and map a public route to it via isql/VHOST_DEFINE. Use for deploying or updating an already-built OpenLink-style portfolio site onto a WebDAV target, verifying a live route serves the generated content, or producing a deployable bundle when live credentials aren't available. Triggers on phrases like "publish this site to WebDAV", "deploy the generated site", "set up an initial OpenLink site from a WebDAV root", "is this site live", or any request to push a pre-rendered static site onto a Virtuoso WebDAV/VHOST target. Does not author or edit RDF content, run the SHACL/Jinja2 pipeline's internal logic, or render pages live from SPARQL — see references/rdf-content-mode.md for the boundary.

What is ai-agent-skills?

ai-agent-skills is a Claude Code agent skill that publish a static HTML/CSS/JS website (generated by an RDF-Turtle + SHACL + Jinja2 pipeline, e.g. sites/openlink/rdf/build.py) to a Virtuoso WebDAV root and map a public route to it via isql/VHOST_DEFINE. Use for deploying or updating an already-built OpenLink-style portfolio site onto a WebDAV target, verifying a live route serves the generated content, or producing a deployable bundle when live credentials aren't available. Triggers on phrases like "publish this site to WebDAV", "deploy the generated site", "set up an initial OpenLink site from a WebDAV root", "is this site live", or any request to push a pre-rendered static site onto a Virtuoso WebDAV/VHOST target. Does not author or edit RDF content, run the SHACL/Jinja2 pipeline's internal logic, or render pages live from SPARQL — see references/rdf-content-mode.md for the boundary.

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OpenLinkSoftware/ai-agent-skills/tree/HEAD/website-from-webdav

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Website From WebDAV

Use this skill to take a static, already-generated HTML/CSS/JS site — the output of an RDF-Turtle + SHACL + Jinja2 pipeline such as sites/openlink/rdf/build.py — and publish it to a Virtuoso WebDAV root with a mapped public route. The site's content model (ontology, SHACL shapes, ordering) is authored and validated upstream of this skill; this skill's job starts at "here is a generated/ folder of finished HTML, get it live."

This is deliberately narrower than weblog-from-webdav or osdi-inclusion-engine: those skills render content live on every request (WebDAV-collection enumeration inside a bespoke VSP, or a shared XSLT skin wrapping WebDAV-sourced HTML). This skill's content is already fully rendered before it arrives — publishing it is a plain WebDAV PUT plus a static-file VHOST_DEFINE, no VSP execution and no live SPARQL query involved. See references/webdav-website-engine-gate.md for why that choice was made and when it would change.

Blocking Gate — A Local File Is Not a Valid Completion

When the user asks to publish, deploy, or "make live" a generated site, a description of what would happen, or a claim that files exist locally, is not a valid completion unless the user explicitly asks only for a local/preview check. The default deliverable is either:

  • a live, verified published site: the target route returns the same content as the local generated/ output, or
  • a complete deployable bundle: generated/*.html, the site's assets/ (site.css, theme.js), a path-substituted templates/deploy-website-route.sql, and a README/run-notes file — with scripts/validate_generated_website_bundle.py passing against it.

Read references/webdav-website-engine-gate.md before claiming either. If deployment credentials (WebDAV and/or isql) are unavailable, stop at the deployable bundle and say plainly that live publication is blocked by missing authenticated access — do not describe hypothetical success.

First decisions

Establish these before publishing anything:

  • Source pipeline directory (e.g. {LLM_ROOT}/Claude Generated/sites/openlink/) and whether rdf/build.py needs to be (re)run first, or generated/ is already current.
  • Target DAV collection, e.g. /DAV/home/kidehen/sites/openlink/.
  • Public route, e.g. https://linkeddata.uriburner.com/sites/openlink/.
  • Identity mode: SQL username/password, isql WebID-TLS, WebID-TLS plus delegation, WebDAV username/password, WebDAV mTLS, or WebDAV mTLS plus On-Behalf-Of.
  • Whether this is a first-time route bootstrap (isql + deploy-website-route.sql required) or a content-only republish of an existing route (WebDAV publish only, per references/webdav-publish-mode.md's out-of-scope boundary — it never touches route configuration).

Workflow

  1. Confirm the source generated/ output is current: if asked to (re)build, run the pipeline's own rdf/validate_shacl.py then rdf/build.py unchanged — this skill does not reimplement or bypass that validation. See references/rdf-content-mode.md.
  2. Before writing anything, search prior memory/work for validate_generated_weblog_bundle, VHOST_DEFINE, DAV_RES_UPLOAD_STRSES_INT, website-from-webdav; reuse proven fixes rather than rediscovering them.
  3. Determine whether the target route already exists (query DB.DBA.HTTP_PATH and WS.WS.SYS_DAV_RES for the target path via isql). If not, bootstrap it: patch templates/deploy-website-route.sql with the real DAV path/route/title, then run it with isql. See references/isql-route-mode.md.
  4. Validate the bundle with scripts/validate_generated_website_bundle.py before any publish. This is blocking.
  5. Publish via WebDAV using scripts/publish_static_bundle.py — PUTs generated/*.html and assets/* recursively, verifies each write with a follow-up GET/PROPFIND compare. See references/webdav-publish-mode.md.
  6. Verify: fetch the live route, confirm the served bytes match the local generated/ file (allowing only whitespace/attribute-order differences, same tolerance the pipeline's own Phase 1 verification uses), confirm assets/site.css/theme.js load with correct MIME types.
  7. Report exactly what happened: route bootstrapped or reused, files published with their DAV paths, verification result — or, if credentials were missing, the deployable bundle location and the explicit statement that live publication did not happen.

Mode references

  • For route bootstrap and isql TLS/WebID syntax, read references/isql-route-mode.md.
  • For WebDAV publication and its out-of-scope boundary, read references/webdav-publish-mode.md.
  • For the pipeline boundary (what this skill does and does not touch in rdf/), read references/rdf-content-mode.md.
  • For why live SPARQL/VSP rendering was not chosen, read references/webdav-website-engine-gate.md.
  • For agent-callable operations, read references/opal-tool-mode.md — not implemented in v1; documented as the extension point.

Practical guardrails

  • Never run rdf/build.py's SHACL validation with warnings suppressed or --skip-validate-style shortcuts; a failing gate means the content is not ready to publish, not an obstacle to route around.
  • DB.DBA.DAV_RES_UPLOAD_STRSES_INT (if used for any non-static-file step) needs a string_output() stream populated via http(content, stream), not a plain string.
  • Avoid $1, $2, and similar tokens in any SQL loaded through isql; they can trigger macro substitution.
  • A static-file VHOST_DEFINE uses is_dav=>1, is_brws=>0, def_page=>'index.html' — do not set def_page=>'index.vsp' unless a VSP entry point genuinely exists; a def_page pointing at a nonexistent VSP resource serves raw source or a 404, not the site.
  • Delete any prior conflicting VHOST_DEFINE for the same host/path before redefining it, same as weblog-from-webdav's route-remove-then-redefine pattern.
  • Treat the certificate-bearing identity and the delegated principal as separate facts: -X/--cert identifies the calling agent, -W/On-Behalf-Of identifies the WebID principal whose ACL rights should be evaluated.
  • Never write credentials into the skill, generated SQL, logs, or committed files.
  • Remote uploads and removals follow the same HTTP → WebDAV → iSQL order and principal PKCS#12 pattern (howto/remote-webdav-upload.ttl, howto/remote-resource-removal.ttl, prefs Steps 215–216). URIBurner writes/deletes go to port 5443. Report :5443 GET and public 443 GET separately after PUT; after DELETE, verify the user-named URL, slash variants, :5443 GET, and the folder listing before claiming removal.

Safe Virtuoso ISQL Error Output

Whenever this workflow invokes OpenLink's native Virtuoso isql client, pass the bare client option COMMAND_TEXT_ON_ERROR=OFF, for example isql {host}:{port} {user} {password} COMMAND_TEXT_ON_ERROR=OFF {script.sql}. Place it before SQL input, script arguments, or -i. OFF maps to the client's internal value 0 and prevents the client from appending the full failed command to error output. The client accepts OFF/ON; literal 0 is rejected. Do not use -u for this setting (-u populates the U argument array). This does not redact secrets from server diagnostics, shell history, or process arguments, so keep credentials out of SQL text and logs. Do not apply this Virtuoso-specific option to unixODBC/iODBC isql clients.

Related Skills