Skip to content

shortcuts

Classes

SkillSyncResult

Bases: BaseModel

Outcome of syncing one skill file via Shortcuts.sync_skill or one file within a directory via Shortcuts.sync_skills.

Attributes:

Name Type Description
action Literal['created', 'updated', 'unchanged', 'skipped', 'failed']

What happened to this file. "created" (new skill written), "updated" (existing skill changed), "unchanged" (matched and identical, no write performed), "skipped" (file was not a valid skill or was a duplicate name, never sent to the server), or "failed" (a server error occurred while syncing this file).

id Optional[str]

The Open WebUI id of the skill, when known.

name Optional[str]

The display name of the skill, when known.

path Optional[str]

The local file path this result refers to.

error Optional[str]

For "skipped"/"failed", the reason or error message.

Shortcuts

Shortcuts(client: OpenWebUI)

A collection of convenience methods (shortcuts) that combine multiple API calls into single, easy-to-use workflows.

Access these via client.shortcuts.

Source code in src/owui_client/shortcuts.py
def __init__(self, client: "OpenWebUI"):
    self.client = client

Functions

sync_skill
sync_skill(path: str | Path) -> SkillSyncResult

Idempotently sync a local skill Markdown file to Open WebUI.

Parses the file (see owui_client.skillfiles), then reconciles it against existing skills returned by SkillsClient.export_skills:

  • No match (by id or by name): creates the skill. If the server rejects the create with an ID_TAKEN (HTTP 400) error — indicating another skill already owns that id but was not returned by export — falls back to updating the skill by id.
  • Match, unchanged: content, name, and description all equal the existing skill. Returns "unchanged" and performs NO write, so the server's updated_at is not bumped.
  • Match, changed: updates the matched skill by id.

Never deletes, toggles, or modifies access grants. When updating, access_grants is omitted (None + exclude_none) so existing grants on the server are preserved.

Parameters:

Name Type Description Default
path str | Path

Path to a Markdown skill file with YAML frontmatter (non-empty name and description).

required

Returns:

Type Description
SkillSyncResult

SkillSyncResult: The action taken plus the skill's id and name.

Raises:

Type Description
`InvalidSkillFileError`

If the file has no/invalid frontmatter or is missing a required field (propagated from parse_skill_file).

HTTPStatusError

On any other server error (re-raised).

Examples:

result = await client.shortcuts.sync_skill("skills/summarizer.md")
print(result.action, result.id)
Source code in src/owui_client/shortcuts.py
async def sync_skill(self, path: str | Path) -> SkillSyncResult:
    """Idempotently sync a local skill Markdown file to Open WebUI.

    Parses the file (see `owui_client.skillfiles`), then reconciles it
    against existing skills returned by `SkillsClient.export_skills`:

    - **No match** (by id or by name): creates the skill. If the server
      rejects the create with an ``ID_TAKEN`` (HTTP 400) error — indicating
      another skill already owns that id but was not returned by export —
      falls back to updating the skill by id.
    - **Match, unchanged**: content, name, and description all equal the
      existing skill. Returns ``"unchanged"`` and performs NO write, so the
      server's ``updated_at`` is not bumped.
    - **Match, changed**: updates the matched skill by id.

    Never deletes, toggles, or modifies access grants. When updating,
    ``access_grants`` is omitted (``None`` + ``exclude_none``) so existing
    grants on the server are preserved.

    Args:
        path: Path to a Markdown skill file with YAML frontmatter
            (non-empty ``name`` and ``description``).

    Returns:
        `SkillSyncResult`: The action taken plus the skill's id and name.

    Raises:
        `InvalidSkillFileError`: If the file has no/invalid frontmatter or is
            missing a required field (propagated from `parse_skill_file`).
        httpx.HTTPStatusError: On any other server error (re-raised).

    Examples:
        ```python
        result = await client.shortcuts.sync_skill("skills/summarizer.md")
        print(result.action, result.id)
        ```
    """
    parsed = parse_skill_file(path)

    form = SkillForm(
        id=parsed.id,
        name=parsed.name,
        description=parsed.description,
        content=parsed.content,
        meta=SkillMeta(tags=[]),
        is_active=True,
        access_grants=None,
    )

    existing = await self.client.skills.export_skills()
    by_id = {skill.id: skill for skill in existing}
    by_name = {skill.name: skill for skill in existing}

    return await self._reconcile_skill(path, form, by_id, by_name)
sync_skills
sync_skills(dir_path: str | Path) -> list[SkillSyncResult]

Recursively sync every valid skill file under a directory.

Discovers all .md files under dir_path (arbitrary depth) via discover_skill_files, then reconciles each valid skill against the server using the same create / unchanged / update logic as Shortcuts.sync_skill. Existing skills are fetched once via SkillsClient.export_skills.

  • Files that are not valid skills, or that collide on a duplicate normalized id, are reported as "skipped" and never sent to the server.
  • A server error on one file is recorded as "failed" and does not abort the rest of the directory.
  • Nothing is ever deleted, toggled, or access-changed.

Parameters:

Name Type Description Default
dir_path str | Path

Directory to scan recursively.

required

Returns:

Type Description
list[SkillSyncResult]

A list of SkillSyncResult (one per .md file), sorted by path.

Raises:

Type Description
`InvalidSkillFileError`

If dir_path does not exist or is not a directory (propagated from discover_skill_files).

Source code in src/owui_client/shortcuts.py
async def sync_skills(self, dir_path: str | Path) -> list[SkillSyncResult]:
    """Recursively sync every valid skill file under a directory.

    Discovers all ``.md`` files under `dir_path` (arbitrary depth) via
    `discover_skill_files`, then reconciles each valid skill against the
    server using the same create / unchanged / update logic as
    `Shortcuts.sync_skill`. Existing skills are fetched once via
    `SkillsClient.export_skills`.

    - Files that are not valid skills, or that collide on a duplicate
      normalized id, are reported as ``"skipped"`` and never sent to the
      server.
    - A server error on one file is recorded as ``"failed"`` and does not
      abort the rest of the directory.
    - Nothing is ever deleted, toggled, or access-changed.

    Args:
        dir_path: Directory to scan recursively.

    Returns:
        A list of `SkillSyncResult` (one per ``.md`` file), sorted by path.

    Raises:
        `InvalidSkillFileError`: If `dir_path` does not exist or is not a
            directory (propagated from `discover_skill_files`).
    """
    discovered = discover_skill_files(dir_path)

    results: list[SkillSyncResult] = [
        SkillSyncResult(action="skipped", path=str(d.path), error=d.skip_reason)
        for d in discovered
        if d.parsed is None
    ]
    to_sync = [d for d in discovered if d.parsed is not None]

    by_id: dict[str, SkillModel] = {}
    by_name: dict[str, SkillModel] = {}
    if to_sync:
        existing = await self.client.skills.export_skills()
        by_id = {skill.id: skill for skill in existing}
        by_name = {skill.name: skill for skill in existing}

    for d in to_sync:
        parsed = d.parsed
        form = SkillForm(
            id=parsed.id,
            name=parsed.name,
            description=parsed.description,
            content=parsed.content,
            meta=SkillMeta(tags=[]),
            is_active=True,
            access_grants=None,
        )
        try:
            results.append(
                await self._reconcile_skill(d.path, form, by_id, by_name)
            )
        except HTTPStatusError as e:
            results.append(
                SkillSyncResult(
                    action="failed",
                    path=str(d.path),
                    id=form.id,
                    name=form.name,
                    error=str(e),
                )
            )

    results.sort(key=lambda r: r.path or "")
    return results

Functions