# localskills.sh — Full Documentation > localskills.sh is a platform for creating, sharing, and installing reusable agent skills and rules for AI coding tools. Publish a skill once as a plain-Markdown package (a folder with a root SKILL.md), then install it into Cursor, Claude Code, Codex CLI, Windsurf, Cline, GitHub Copilot, OpenCode, or Aider with one CLI command. This file contains the complete localskills.sh documentation as a single plain-text document for LLM consumption. A shorter index with links is at https://localskills.sh/llms.txt. The REST API is described in full by the OpenAPI 3.1 specification at https://localskills.sh/openapi.yaml. --- # Getting Started Source: https://localskills.sh/docs/getting-started localskills.sh is a platform for creating, sharing, and installing reusable agent skills and rules for AI coding tools. Publish a skill once and install it into Cursor, Claude Code, Codex CLI, Windsurf, Cline, GitHub Copilot, OpenCode, or Aider — the CLI writes each tool's native format. (Video: localskills.sh in 45 seconds — install, publish, folders, and organization features.) ## 1. Install the CLI Install the `localskills` CLI globally via npm: ```bash $ npm i -g @localskills/cli ``` Or use `pnpm`, `yarn`, or `bun`: ```bash $ pnpm add -g @localskills/cli $ yarn global add @localskills/cli $ bun add -g @localskills/cli ``` Verify the installation: ```bash $ localskills --version ``` ## 2. Authenticate Running `localskills login` starts a browser device-code flow — the CLI shows a verification code, opens your browser, and waits for approval: ```bash $ localskills login ``` Your token is stored in `~/.localskills/config.json`. For headless environments — CI servers, containers, or SSH sessions — create an API token in the dashboard and pass it directly: ```bash $ localskills login --token lsk_your_token_here ``` Verify your identity at any time: ```bash $ localskills whoami ``` ## 3. Install your first skill Install a skill by slug. The CLI walks you through platform selection, scope (global or project), and install method: ```bash $ localskills install my-skill ``` Skip the prompts by passing flags directly: ```bash $ localskills install my-skill -t cursor,claude --project --symlink ``` Run `localskills install` without a slug to browse available skills interactively, or explore the public catalog at `localskills.sh/explore`. ## 4. Publish your own Publish a single file, or a whole skill folder (a directory with a `SKILL.md` plus scripts and references) as a multi-file package: ```bash $ localskills publish .claude/skills/my-skill --visibility public ``` Every publish creates a new version you can roll back from the dashboard. Skills default to *private* — visible only to your organization. > Prefer working in the browser? The dashboard's **New Skill** page accepts markdown directly or a drag-and-dropped folder/zip — no CLI required. ## Where to next **Agents, not humans?** Connect your AI agent straight to the platform with the `https://localskills.sh/docs/mcp` MCP server — agents can search, read, and publish skills without leaving their workflow. **Working with a team?** Organizations add folders with access control, teams, custom roles, SSO, SCIM, audit logs, and GitHub Sync — start at `https://localskills.sh/docs/teams`. **Publishing from CI?** Exchange your pipeline's OIDC token for platform access with no stored secrets — see `https://localskills.sh/docs/cicd`. --- # CLI Reference Source: https://localskills.sh/docs/cli The `localskills` CLI (`@localskills/cli` on npm) installs, publishes, and organizes skills across AI coding tools from your terminal. ## login ```bash $ localskills login [--token ] [--oidc-token --team ] ``` With no flags, starts a browser device-code flow: the CLI shows a verification code, opens the browser, and polls until you approve. Device codes expire after 10 minutes. Credentials are stored in `~/.localskills/config.json` with `0600` permissions. | Flag | Description | |------|-------------| | `--token ` | Headless mode — store an API token directly (CI, containers, SSH) | | `--oidc-token ` | Exchange a CI/CD OIDC identity token for a short-lived API token | | `--team ` | Organization slug (required with `--oidc-token`) | See `https://localskills.sh/docs/cicd` for the OIDC exchange — it lets GitHub Actions and GitLab CI publish without storing any secret. ## logout ```bash $ localskills logout ``` Clear the stored auth token from your local configuration. ## whoami ```bash $ localskills whoami ``` Display the current authenticated user's name, username, and email. ## install ```bash $ localskills install [slug] [options] ``` Install a skill by slug, public ID, or share URL. With no arguments, the CLI lists available skills for interactive selection; with no flags it prompts for platforms, scope, and install method. Pin a version npm-style: `my-skill@1.2.3` (exact), `my-skill@^1.0.0` (semver range), or `my-skill@latest`. | Flag | Description | |------|-------------| | `-t, --target ` | Target platforms: cursor, claude, codex, windsurf, cline, copilot, opencode, aider | | `-g, --global` | Install globally (user-level) | | `-p, --project [dir]` | Install in a project directory | | `--symlink` | Symlink from the local cache (default on macOS/Linux) | | `--copy` | Copy content directly into the target location (default on Windows) | > GitHub Copilot and Codex CLI use the *section* install method (embedded markers in a shared file). Windsurf global installs also use sections. Multi-file package skills install to Claude Code only — other platforms are skipped with a notice. ## uninstall ```bash $ localskills uninstall [slug] [--purge] ``` Remove an installed skill from all target platforms. Pass `--purge` to also delete the cached copy from `~/.localskills/cache/`. Omit the slug for interactive selection. ## list ```bash $ localskills list [--public] [--tag ] [--search ] ``` List skills available to you from your organizations. Pass `--public` to browse public skills instead; `--tag` and `--search` filter the public catalog. ## pull ```bash $ localskills pull [slug] ``` Update installed skills to the latest version. Omit the slug to update everything. The CLI compares content hashes to decide whether an update is needed — symlinked installs update automatically when the cache refreshes, while copied and section-based installs are re-written in place. ## publish ```bash $ localskills publish [path] [options] ``` Publish a local skill to localskills.sh. `path` can be a single file (published as a text skill, frontmatter stripped) or a folder (zipped and published as a multi-file **package**, files preserved verbatim). A skill folder must contain a `SKILL.md` at its root. Package limits: 100 MB compressed, 100 MB uncompressed, 500 files. Without a `path`, the CLI scans your project for unpublished skills across all supported platform directories and lets you pick interactively. | Flag | Description | |------|-------------| | `-t, --team ` | Organization ID or slug to publish to | | `-n, --name ` | Skill display name | | `--visibility ` | public, private, or unlisted (default: private) | | `--type ` | skill or rule (default: skill) | | `-m, --message ` | Message for the initial version (default: "Initial version") | | `-f, --folder ` | Folder path to publish into (e.g. marketing/seo) | | `--personal` | Publish privately into your managed Personal Workspace | | `-p, --parents` | Create missing folders in the --folder path | ## push ```bash $ localskills push --skill [options] ``` Push a new version of an *existing* skill — the non-interactive counterpart to `publish`, designed for CI pipelines. `path` accepts a file or a skill folder, same as publish. | Flag | Description | |------|-------------| | `-s, --skill ` | Skill ID or slug (required) | | `--version ` | Explicit semver (must be greater than the current version) | | `--patch` | Bump the patch version | | `--minor` | Bump the minor version | | `--major` | Bump the major version | | `-m, --message ` | Version message | ```bash $ localskills push skill.md --skill my-skill --patch -m "Fix typos" ``` ## share ```bash $ localskills share [path] [options] ``` | Flag | Description | |------|-------------| | `-n, --name ` | Skill display name | | `--type ` | skill or rule (default: skill) | Share a skill anonymously without creating an account — `path` accepts a file or a skill folder. On first use, the CLI generates an Ed25519 keypair stored in `~/.localskills/config.json` as your anonymous identity. Shared skills are always **unlisted** and anonymous identities are limited to 10 shared skills. See `https://localskills.sh/docs/sharing` for details. ## folders ```bash $ localskills folders [list|create ] [-t ] ``` List an organization's skill folders with their skill counts (`list` is the default subcommand), or create a folder path — missing parent folders are created along the way, like `mkdir -p`. ## move ```bash $ localskills move [--parents] ``` Move a skill (by public ID or slug) into a folder, or back to the root with `/`. Pass `--parents` to create the destination path first: ```bash $ localskills move my-skill marketing/seo $ localskills move my-skill / $ localskills move my-skill new/place --parents ``` ## profile ```bash $ localskills profile |switch |delete > ``` Manage CLI profiles for multiple accounts — for example a personal account and a work organization. Each profile keeps its own auth token, installed skills, and preferences. Profile names are lowercase alphanumeric with hyphens, up to 32 characters. ```bash $ localskills profile create work $ localskills profile switch work $ localskills profile list ``` Override the active profile per command with the global `--profile ` flag or the `LOCALSKILLS_PROFILE` environment variable (flag wins over the environment, which wins over the config) — useful in scripts and CI. ## Environment variables | Variable | Description | |----------|-------------| | `LOCALSKILLS_TOKEN` | Auth token override — takes precedence over the stored token, never persisted (CI-friendly) | | `LOCALSKILLS_API_URL` | API base URL override (default: https://localskills.sh) | | `LOCALSKILLS_PROFILE` | Active profile override | | `LOCALSKILLS_NO_UPDATE_CHECK` | Set to 1 to disable the daily update check | ## Configuration layout Config and cache live in `~/.localskills/`: ```text ~/.localskills/ config.json # Auth token(s), profiles, installed skills, preferences cache/ my-skill/ raw.md # Original content (text skills) meta.json # Hash, version, metadata cursor.mdc # Platform-specific transformed files my-folder-skill/ meta.json manifest.json # File list for a package skill pkg/ # Unpacked package skill (SKILL.md + scripts/, ...) ``` --- # MCP Server Source: https://localskills.sh/docs/mcp localskills.sh provides a **Model Context Protocol (MCP)** server that lets AI agents discover, read, and author your teams' skills without leaving their workflow. Any MCP client that supports Streamable HTTP with OAuth — Claude Code, claude.ai, Claude Desktop, Cursor, Codex CLI, Windsurf, VS Code, and others — can connect directly. ## Server endpoint The MCP server is available at: ``` https://localskills.sh/mcp/mcp ``` It uses the **Streamable HTTP** transport (stateless mode). Each request is independent — no session management required. ## Authentication The MCP server requires **OAuth 2.1**. Unauthenticated requests receive a 401 with the standard `WWW-Authenticate` discovery hint, so well-behaved MCP clients handle the whole flow automatically: they register dynamically, open a browser authorization page, and receive a Bearer access token scoped to the MCP resource. During authorization you pick which of your **organizations** the connection is for. The token is bound to that organization — the agent sees and writes only that org's skills. To work with a different organization, re-authenticate and pick it (most clients have a "reconnect" or "log out" action per server). The server supports these OAuth scopes: | Scope | Description | | --- | --- | | skills:read | Search skills and fetch their content (always granted) | | skills:write | Create skills and publish versions — optional; uncheck it on the consent screen for a read-only connection | | offline_access | Refresh tokens, so the client stays connected without re-authorizing | | openid / profile / email | Basic identity for the consent screen | Discovery metadata lives at the standard well-known URLs: ``` https://localskills.sh/.well-known/oauth-authorization-server https://localskills.sh/.well-known/oauth-protected-resource/mcp/mcp ``` > API tokens (`lsk_` / `lskt_`) are not accepted by the MCP endpoint, and clients that cannot complete a browser OAuth flow cannot connect. For headless automation, use the REST API or the CLI instead. ## Client setup Point any MCP client's Streamable HTTP transport at `https://localskills.sh/mcp/mcp`. The first connection triggers the browser OAuth flow. Verified setups for common environments: ### Claude Code Add the server via the CLI (global): ```bash claude mcp add localskills --transport http https://localskills.sh/mcp/mcp ``` Or share it with your whole project by committing a `.mcp.json` at the repo root: ```json { "mcpServers": { "localskills": { "type": "http", "url": "https://localskills.sh/mcp/mcp" } } } ``` Run `/mcp` inside a session to authenticate and inspect the connection. ### claude.ai & Claude Desktop Go to **Settings → Connectors → Add custom connector** and enter `https://localskills.sh/mcp/mcp`. Approve the OAuth prompt and the tools become available in chats (enable them from the search-and-tools menu). On Team/Enterprise plans an admin may need to add the connector first. ### Cursor Add to `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` globally): ```json { "mcpServers": { "localskills": { "url": "https://localskills.sh/mcp/mcp" } } } ``` ### Codex CLI Add to `~/.codex/config.toml`: ```toml [mcp_servers.localskills] url = "https://localskills.sh/mcp/mcp" ``` Then run `codex mcp login localskills` to complete the OAuth flow. ### Windsurf Add to `~/.codeium/windsurf/mcp_config.json` (note the `serverUrl` key): ```json { "mcpServers": { "localskills": { "serverUrl": "https://localskills.sh/mcp/mcp" } } } ``` ### VS Code (Copilot agent mode) Add to `.vscode/mcp.json` in your workspace: ```json { "servers": { "localskills": { "type": "http", "url": "https://localskills.sh/mcp/mcp" } } } ``` ### Cline Add to `~/.cline/data/settings/cline_mcp_settings.json`: ```json { "mcpServers": { "localskills": { "type": "streamableHttp", "url": "https://localskills.sh/mcp/mcp" } } } ``` ### opencode Add to `opencode.json` in your project (or `~/.config/opencode/opencode.json` globally): ```json { "mcp": { "localskills": { "type": "remote", "url": "https://localskills.sh/mcp/mcp", "enabled": true } } } ``` > Rolling this out to a whole team? Register the server once as an organization MCP server (see https://localskills.sh/docs/mcp-servers) and members run `localskills mcp pull` — the CLI writes the correct config format for every tool above automatically. ## Available tools The server exposes up to eleven tools (`create_upload_ticket` registers only when `UPLOAD_SIGN_SECRET` is configured on the worker). Skill and folder read tools require the `skills:read` scope; `whoami` requires only an authenticated token, not a skills scope. Write tools require `skills:write` and are not registered at all on read-only connections. ### whoami Returns the identity behind the token: user id, email, display `name`, profile `username` (null until the user picks one), granted scopes, and your teams (each with an id, slug, name, and role). When the token is bound to one organization, `boundTeamId` identifies it. Takes no arguments; agents call it first to learn which `team` values they can pass to the tools below, and integrations that keep a per-member folder can derive its name from `username`/`name` instead of guessing from the email. ### search_skills Search your teams' skills and rules. Returns matching skills with their slug, publicId, description, and current version — pass one to `get_skill_content` to load it. The public catalog is not searchable over MCP. Use `scope: "personal"` to search only the authenticated member's managed Personal Workspace, `scope: "mine"` for skills authored by that member, or `scope: "team"` for the readable organization library. Personal scope is a filter on the existing deterministic search tool, not a separate tool. | Parameter | Description | | --- | --- | | query | Search text to match against name, description, and tags | | type | Filter by "skill" or "rule" | | tag | Filter by a specific tag | | team | Limit to one of your teams (tenant slug or id, from whoami) | | folder | Limit to a folder subtree, e.g. "backend/database" — requires team | | limit | Max results, 1–50 (default 20) | ### get_skill_content Load a skill's full content. Returns the SKILL.md body plus any additional package files — text files inlined up to a size budget, binaries listed as metadata with a signed `downloadUrl` (see "Bundled files & signed downloads"). Multi-file packages also include a signed `packageDownloadUrl` for the whole zip. | Parameter | Description | | --- | --- | | slug | The skill slug, or its publicId when the slug is ambiguous (required) | | version | Version: exact semver (1.2.3), range (^1.0.0), or integer | > Sibling skills in different folders can share a slug. An ambiguous slug returns error code `E_AMBIGUOUS` listing the candidates — retry with the publicId from search results. ### get_skill_file Fetch one file from a skill's package by path — for binary files that `get_skill_content` lists as metadata only. Returns the bytes as base64 (chunked, 32 KiB by default, up to 1 MiB per call) plus a signed `downloadUrl` that streams the whole file in one HTTP GET. Includes a SHA-256 checksum when the file is returned whole. | Parameter | Description | | --- | --- | | slug | The skill slug, or its publicId (required) | | path | File path inside the package, as listed by get_skill_content (required) | | version | Version: exact semver, range, or integer | | offset | Byte offset to start from (default 0) — for chunked reads | | length | Max bytes to return (default 32 KiB, hard cap 1 MiB) | ### list_skill_versions List a skill's published versions, newest first, so you can pin one instead of guessing a semver. Each entry carries `version`, `semver`, `isCurrent`, `isPrerelease`, `createdAt`, `fileCount`, and `contentHash`; pass a returned `semver` (or `version`) to `get_skill_content` or `get_skill_file`. Prereleases are hidden unless `includePrerelease` is set or `spec` names one. | Parameter | Description | | --- | --- | | slug | The skill slug, or its publicId when the slug is ambiguous (required) | | spec | Only list versions satisfying this spec: exact version, range (^1.2.0, 1.2.x), integer, "*", or "latest" | | limit | Max versions to return (default 30, max 100) | | cursor | Pagination cursor from a previous call | | includePrerelease | Include prerelease versions (hidden by default) | ### list_folders Browse a team's folder tree (only paths you can read). Pass a returned path as the `folder` argument to `search_skills` to scope a search. | Parameter | Description | | --- | --- | | team | Tenant slug or id, from whoami or search results (required) | ### get_folder_access Who can read a folder: every team member with access, resolved live from the folder ACLs (direct grants, team grants, and admin-bypass roles), plus the underlying grant list. Open folders report all members as `access: "open"`. Readable by anyone who can read the folder; editing ACLs stays on the website. | Parameter | Description | | --- | --- | | team | Tenant slug or id, from whoami or search results (required) | | folder | Folder path (e.g. "individual/jane-doe") or folder ID (required) | ### create_skill Create a new skill. Requires the `skills:write` scope and skill-creation permission in the target organization. Idempotent on content: retrying an identical create after a lost response returns the existing skill with `idempotent: true` instead of creating a duplicate. | Parameter | Description | | --- | --- | | name | Skill name, 1–100 characters (required) | | content | The root SKILL.md body in markdown (required unless uploadId is passed) | | files | Supporting package files [{ path, content, encoding? }] — utf8 by default, base64 for binaries; max 100 files / 10 MB per call | | uploadId | Commit a package staged via create_upload_ticket (up to 100 MB); mutually exclusive with content/files | | tenantId | Team ID to create the skill under (required) | | type | "skill" or "rule" (default: skill) | | visibility | "public", "private", or "unlisted" (default: private) | | tags | Up to 10 tags | | description | Short description | | folder | Folder path (e.g. "backend/database") or folder ID (default: tenant root). Must exist unless createParents is set | | createParents | Create the `folder` path when it does not exist yet, including missing ancestors (like `localskills publish --parents`); requires folder:create in the team. Ignored for folder IDs and `destination: "personal"` | ### create_folder Ensure a folder path exists in a team, creating any missing ancestors (`mkdir -p`, the MCP twin of `localskills folders create`). Idempotent: an existing path is returned with `created: false`, so an agent can guarantee a destination before `create_skill` without a separate existence check (or pass `createParents` to `create_skill` and skip this tool). Requires the `skills:write` scope and folder-creation permission in the team; when the nearest existing ancestor is governed by a folder ACL you need write access on it. Managed Personal Workspaces folders (the namespace root and each member workspace) refuse children; target those with `create_skill`'s `destination: "personal"` instead. Returns the `folder` (id, actual slugified `path`, name, restricted, parentId), `created`, and the `createdPaths` this call made. | Parameter | Description | | --- | --- | | team | Tenant slug or id, from whoami or search results (required) | | path | Folder path to ensure, e.g. "individual/jane-doe"; segments are slugified (required) | ### create_upload_ticket Stage a full-size package (up to 100 MB / 500 files) when the inline `files` cap is too small. Returns a `fileUrlTemplate` (PUT each raw file, substituting `{path}` URL-encoded per segment), a `finalizeUrl` (POST once all files are uploaded — the server assembles the canonical deterministic zip), and the `uploadId` to pass to `create_skill` / `publish_version`. Requires HTTP egress and the `skills:write` scope; no parameters. Clients never build zip archives — packaging is server-side. ### publish_version Publish a new version of an existing skill. Requires the `skills:write` scope. Auto-bumps the patch version by default, or you can specify an explicit semver or bump type. Idempotent on content: publishing bytes identical to the current version (without a higher explicit semver) returns the current version with `idempotent: true`. | Parameter | Description | | --- | --- | | slug | Skill slug (required) | | content | The new root SKILL.md body for this version (required unless uploadId is passed) | | files | Supporting package files [{ path, content, encoding? }] — utf8 by default, base64 for binaries; max 100 files / 10 MB per call | | uploadId | Commit a package staged via create_upload_ticket (up to 100 MB); mutually exclusive with content/files | | semver | Explicit semver (must be greater than current) | | bump | Auto-bump: "major", "minor", or "patch" (default: patch) | | message | Version message / changelog | ## Resources The server also provides MCP **resources** for browsing skills: `localskills://skills` — Lists all skills your token can read, with metadata (slug, name, description, type, semver). `localskills://skills/{slug}` — Returns the SKILL.md content of an individual skill. Agents can browse the list, then read individual skills to inject content into their context. `localskills://skills/{slug}/files/{path}` — Reads one file from a skill's package (current version). Binary files come back as base64 blob contents; files over 1 MiB must be fetched with `get_skill_file` instead. ## Bundled files & signed downloads Skills are packages — a root SKILL.md plus optional supporting files (scripts, templates, binary assets). Agents have two ways to materialize those files: **Signed download URLs** — `get_skill_content` and `get_skill_file` include a `downloadUrl` on every non-inlined file (and a `packageDownloadUrl` for the whole zip). These are HMAC-signed URLs, valid for 1 hour, minted only after the caller's access checks pass. One `curl -o file ''` streams the file at any size without passing bytes through model context — the best path whenever the agent's sandbox has HTTP egress. **Inline base64** — the guaranteed fallback for sandboxes without network access (MCP calls ride the client's connection, so they always work). Call `get_skill_file` repeatedly, advancing `offset` by `bytesReturned` until `eof`, then concatenate and decode the chunks. ## Error handling Tool failures set `isError` and carry a machine-readable code in `structuredContent.error.code`: | Code | Meaning | | --- | --- | | E_NOT_FOUND | No such skill, team, folder, or file (or no access to it) | | E_AMBIGUOUS | Slug matches several skills — retry with the publicId | | E_ACCESS_DENIED | Authenticated but not allowed (missing membership or folder grant) | | E_INVALID_INPUT | Bad arguments, e.g. folder without team | ## Dynamic skill loading Agents can discover and load skills on the fly during a conversation. This is useful when an agent encounters a task it doesn't have instructions for — it can search your team's library, pull in the relevant skill, and continue without interruption. **Example workflow** — An agent is asked to write a Next.js API route. It searches for relevant skills, finds one with best practices, and injects it into context: ```js // 1. Agent calls search_skills search_skills({ query: "next.js api routes", type: "skill", limit: 5 }) // 2. Agent picks the best match and fetches its content get_skill_content({ slug: "nextjs-api-patterns" }) // 3. The skill content is now in the agent's context // and it follows the instructions while writing code ``` Because MCP tools are native to the agent's runtime, this happens seamlessly. The agent decides when to search, what to load, and how to apply the instructions — no manual intervention needed. > Skills loaded via MCP are injected into the agent's current context window. They don't persist across sessions unless installed locally via the CLI. ## Loading skills on boot You can configure your agent to automatically load skills from localskills.sh when a session starts. This is done by adding instructions to your project's agent configuration file — `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or equivalent. **CLAUDE.md example** — Add this to your project's `CLAUDE.md` to have Claude Code load skills at the start of every session: ````md # Skills At the start of each session, use the localskills MCP server to load the following skills into context: - `nextjs-api-patterns` — Follow these patterns for all API routes - `drizzle-d1-guide` — Reference for database queries - `testing-conventions` — Standards for writing tests To load a skill, call get_skill_content with the slug above. If you need additional skills for a task, search localskills.sh first. ```` **AGENTS.md example** — For multi-agent setups, you can scope skills to specific agent roles: ````md # Agent Skills ## All agents Load these skills from localskills.sh via MCP at session start: - `code-review-checklist` — Apply during all code reviews - `security-rules` — Enforce on every change ## Frontend agent Additionally load: - `react-component-patterns` — Component architecture guidelines - `tailwind-conventions` — Styling standards ## Backend agent Additionally load: - `api-design-guide` — REST API conventions - `error-handling-patterns` — Error handling standards ```` **.cursorrules example** — Cursor users can add MCP skill loading to their project rules: ``` You have access to the localskills MCP server. At the start of each conversation, load these skills: 1. Call get_skill_content({ slug: "react-component-patterns" }) 2. Call get_skill_content({ slug: "typescript-strict-mode" }) Follow the loaded skill instructions for all code you write. When asked about an unfamiliar topic, search localskills.sh for relevant skills before answering. ``` This pattern lets teams maintain a shared set of coding standards, patterns, and guidelines that every agent session picks up automatically. Update a skill on localskills.sh and every agent gets the latest version on their next session — no reinstall needed. > Boot-loaded skills count against the agent's context window. Keep the list focused — 3-5 skills is a good starting point. Use `search_skills` for on-demand discovery during the session. ## Permissions & access MCP requests act as the user who authorized the connection, within the organization chosen during authorization. The server surfaces only your teams' skills — the public catalog is browsable on the website and REST API, not over MCP. Folder restrictions apply exactly as they do in the dashboard: skills in folders you have no grant for don't appear in search, resources, or reads. Writes (`create_skill`, `publish_version`) additionally require the corresponding skill permissions in the target organization. Revoking access works as you'd expect: leave (or be removed from) an organization and existing tokens bound to it stop returning its skills immediately. ## Health check Verify the server is running: ```bash curl https://localskills.sh/mcp/health ``` Returns `{"status":"ok"}` when healthy. --- # Skills Source: https://localskills.sh/docs/skills Skills are reusable agent instructions for AI coding tools. Rules are governance guidelines or coding standards. Both are versioned, organized into folders, and installable across eight platforms. ## Types A **skill** provides agent instructions, capabilities, or workflows — installed into your tool's skill directory. A **rule** provides governance guidelines or coding standards. On some platforms, rules are placed in a separate directory (e.g. `.claude/rules/`). ## Every skill is a SKILL.md package A skill is a folder with a `SKILL.md` at its root — plus, optionally, any scripts, references, and assets. The `SKILL.md` is the skill's content: its frontmatter `description:` is what shows on the skill's page and cards, and its body renders as the page's primary content. The simplest skill is just the one file — write it in the dashboard editor or publish a Markdown file with the CLI. Multi-file skills are stored as a single versioned archive and unpacked on install. Publish one by dragging a folder or `.zip` onto the New Skill page, or with `localskills publish path/to/folder`. Package limits: **100 MB** compressed, **100 MB** uncompressed, **500 files**. > Package skills install to Claude Code only (they are a directory format). Other platforms are skipped with a notice during install. ## Visibility | Level | Behavior | |-------|----------| | Public | Discoverable in search, profiles, and explore. Installable by anyone. | | Private | Visible only to organization members. Requires authentication to install. | | Unlisted | Accessible via direct link only. Not shown in search or profiles. | Beyond visibility, private skills can be further scoped inside an organization with folder restrictions — see `https://localskills.sh/docs/folders`. ## Versioning Every publish creates a new version with a semantic version label (e.g. `v1.2.0`). Pushing an update bumps the patch version by default; the CLI and API accept `--minor`, `--major`, or an explicit semver. Content is hashed with SHA-256 so the CLI can detect whether an update is available. The dashboard shows the full version history for each skill, and a role with the skill update permission can revert to any previous version with one click. Running `localskills pull` compares the local content hash against the latest version and updates only when they differ. ## Supported platforms | Platform | Project path | Scope | Method | |----------|--------------|-------|--------| | Cursor | `.cursor/rules/{slug}.mdc` | Global + project | Symlink | | Claude Code | `.claude/skills/{slug}/SKILL.md` | Global + project | Symlink | | Codex CLI | `AGENTS.md` | Global + project | Section | | Windsurf | `.windsurf/rules/{slug}.md` | Global + project | Section / symlink | | Cline | `.clinerules/{slug}.md` | Project only | Symlink | | GitHub Copilot | `.github/copilot-instructions.md` | Project only | Section | | OpenCode | `.opencode/rules/{slug}.md` | Global + project | Symlink | | Aider | `.aider/skills/{slug}.md` | Project only | Symlink | The CLI auto-detects which tools are installed and pre-selects them during interactive installs. ## Install methods **Symlink** (default on macOS/Linux) — Creates a symbolic link from the target location to the cached file in `~/.localskills/cache/`. Pulling a new version updates the cache, and all symlinks pick up the latest content automatically. **Copy** (default on Windows) — Writes an independent copy of the skill content into the target location. Running `localskills pull` re-writes the file with updated content. **Section** — Embeds the skill in a shared file using marker comments: ```html ...skill content... ``` Section-based installs are used for platforms that rely on a single shared file (e.g. GitHub Copilot's `copilot-instructions.md` or Codex CLI's `AGENTS.md`). --- # Folders & Access Control Source: https://localskills.sh/docs/folders Every organization has a folder tree for its skills. Folders keep large skill libraries navigable, define the directory layout of synced GitHub repos, and — when restricted — control who can see and edit the skills inside them. ## The folder tree Folders nest arbitrarily deep and are addressed by slug paths like `engineering/reviews`. Skills live either at the organization root or inside exactly one folder. Create and manage folders anywhere you work: the dashboard (**+ New Folder**, and **Rename**, **Move to…**, **Delete** on each row), the CLI, or the API. Skills can be filed into a folder when created, or moved later: ```bash $ localskills folders create engineering/reviews $ localskills publish ./my-skill --folder engineering/reviews $ localskills move my-skill engineering/reviews ``` Folder paths are organizational only — install identifiers (slug and public ID) never change when a skill moves between folders. > Deleting a folder requires it to be empty. Renaming or moving a folder also renames its path in synced GitHub repos. ## Restricting a folder By default, private skills are visible to every organization member. Restricting a folder narrows that: its skills become hidden from everyone except the teams and members you grant access to (and admins, who always retain access). Open **Manage access** from a folder's actions menu, enable **Restrict this folder**, and add grants: A restriction governs the folder *and everything beneath it*. If a subtree contains another restricted folder, the deepest restriction wins for the skills under it — so a broadly shared `engineering/` can still contain a tightly held `engineering/secrets/`. ## Grant levels Grants go to teams or individual members, at one of three levels. Grants only ever *cap* what a person can do — they never add permissions beyond the person's organization roles. | Level | Allows (up to your role's permissions) | | --- | --- | | viewer | Read the folder's skills. | | editor | Read, create, update, and delete skills; manage subfolders. | | manager | Everything editor allows, plus managing the folder's access grants. | Anyone whose role carries the `folders: manageAcl` permission (built into owner and admin) bypasses restrictions entirely, and so do organization API tokens — they act as the organization itself. ## Where restrictions apply Restrictions are enforced everywhere skills are read: the dashboard, share pages, the REST API, the CLI, and the MCP server. A member without a grant doesn't see restricted skills in lists, search, or installs at all. > Restriction wins over visibility: even a skill marked *public* or *unlisted* is served as if private while it sits in a restricted folder — it disappears from explore, search, and its share link for anyone without a grant. One caveat remains: if your organization mirrors skills to GitHub, repo collaborators can read restricted folders in the repo — manage repo access separately. ## Auditing Folder activity is captured in the organization audit log: `folder.created`, `folder.updated`, `folder.deleted`, and `folder.acl_updated` (with the restriction state and grant changes). --- # Anonymous Sharing Source: https://localskills.sh/docs/sharing Share agent skills instantly without creating an account. Think of it as a pastebin for AI agent skills — share a file, get a link. ## Overview The `localskills share` command lets you share a skill file without signing up or logging in. Shared skills are always **unlisted** — accessible only via the direct URL. They never appear in explore or search results. Anyone with the link can install the shared skill using `localskills install`, including users who are not authenticated. ## How it works On first use, the CLI generates an **Ed25519 keypair** stored in `~/.localskills/config.json`. The public key is sent to the server, which creates an anonymous user record and issues an API token. The private key never leaves your machine. If the token is ever lost but the keypair is retained, re-registering with the same public key issues a new token for the same anonymous identity. ## Usage Share a specific file, or a whole skill folder (uploaded as a multi-file package): ```bash $ localskills share ./my-skill.md $ localskills share ./my-skill-folder ``` Or run without arguments to scan for skill files and select interactively: ```bash $ localskills share ``` Options: ```bash # Set a custom name $ localskills share ./skill.md --name "My Custom Skill" # Share as a rule instead of a skill $ localskills share ./rule.md --type rule ``` After sharing, the CLI prints the URL and install command: ```text URL: https://localskills.sh/s/k9Xm2pLq Install: localskills install k9Xm2pLq ``` The `/s/` short link is a permanent identifier: it redirects to the skill's canonical `/{owner}/{skill}` page and keeps working if the skill is later renamed or moved between folders. Team skills are usually shared by their readable path, e.g. `localskills install acme/research/company-lookup`. ## Identity & security Your anonymous identity is an Ed25519 keypair stored in `~/.localskills/config.json` with `0600` permissions (owner read/write only). The server stores only the public key. Your private key never leaves your machine and is never transmitted over the network. > If you delete `~/.localskills/config.json`, your anonymous identity is lost and a new one will be created on the next share. Previously shared skills will remain accessible but will no longer be associated with your new identity. ## Limitations Anonymous sharing has the following restrictions: - Maximum of 10 shared skills per anonymous identity - All shared skills are **unlisted** — no public visibility option - No organization features, analytics, or version history management - No web dashboard access For unlimited publishing, organization features, and full platform access, sign up with `localskills login`. --- # Organizations & Teams Source: https://localskills.sh/docs/teams Organizations group users and skills together with role-based access control. Every skill belongs to an organization. Within an organization, members can be grouped into teams that carry roles. ## Creating an organization Create an organization from the dashboard or via the API. Names must be between 2 and 50 characters. A URL-safe slug is auto-generated from the name — lowercase alphanumeric and hyphens. The user who creates the organization automatically becomes the owner. ## Organization administration Everything organization-level lives under the **Organization** entry in the dashboard sidebar, split into tabs: **Members**, **Roles**, **Teams**, **Tokens**, **CI/CD**, **SSO & SCIM**, **GitHub Sync**, **Audit Log**, and **Settings**. ## Members & invitations Members join by invitation. A role with the invitations permission (built into owner and admin) can invite new members by email; each invitation includes a role assignment and expires after 7 days. The invitee must sign in with the same email address the invitation was sent to, and only owners can invite members with the owner role. Organizations can also turn on auto-join for an email domain (Members tab, Auto-join card). Once the domain is enabled for the organization, anyone who signs in with a verified email address on that domain joins automatically as a member, whichever surface they arrive through (website, CLI, or an MCP connector), with no invitation to accept. A pending invitation to the same organization is consumed instead, and its role wins. Members can hold multiple roles at once — their permissions are the union of everything their roles grant. See [Roles & Permissions](https://localskills.sh/docs/roles) for the built-in roles and how to define custom ones. ## Teams Teams group members inside an organization, and a team can have roles attached: every member of the team gets those roles on top of their own, live. Change the team's attached roles and every member's effective permissions update immediately. The owner role can never be attached to a team. Teams are also the unit of folder access: restricted folders grant access to teams (or individual members) — see [Folders & Access Control](https://localskills.sh/docs/folders). ## SSO group mapping With SAML SSO configured, identity-provider groups map to teams. Team membership is re-synced from the IdP's groups attribute on every SSO login: users are added to matched teams and removed from mapped teams whose group is gone. Teams without a group mapping are never touched by the sync, so manually curated teams work alongside IdP-managed ones. Manage group mappings from the **SSO & SCIM** tab; each sync is recorded in the audit log as `sso.team_sync`. > Map IdP groups to *teams*, then attach roles to those teams — this is how directory group membership translates into permissions on localskills.sh. ## Visibility & publishing policies The **Settings** tab has three organization-level policies: **Public profile page** — whether the organization's profile at `localskills.sh/{slug}` (including folder browsing) is visible to non-members. When off, non-members get a 404; skills explicitly made public stay reachable at their own URLs. **Public member list** — whether the member list (names and roles) appears on the public profile page. Members always see the full list in the dashboard. **Restrict public publishing** — when on, making a skill *public* or *unlisted* requires the `skill: publish` permission, held by the owner and admin built-in roles and grantable to custom roles. Everyone else's skills are private, and the visibility pickers only offer private. Skills that were already public keep their visibility until someone changes it. Members who want a skill published can ask a role holding the permission to flip it. --- # Roles & Permissions Source: https://localskills.sh/docs/roles Access inside an organization is role-based. Four built-in roles cover the common cases, and custom roles compose granular permissions for everything else. ## Built-in roles | Role | Permissions | | --- | --- | | owner | Every permission, plus abilities that can't be delegated: inviting other owners and configuring SSO/SCIM. Protected from removal. | | admin | Every permission in the catalog below — full day-to-day administration. | | member | `skills: read, create` and `folders: create, update, delete` — can use and contribute skills, but not delete them, manage people, or change settings. | | viewonly | Read-only dashboard access. | ## Custom roles Create custom roles from the **Roles** tab. A role has a name (2–40 characters: lowercase letters, digits, hyphens, underscores) and any combination of permissions from the catalog below. > You can only grant permissions you hold yourself — a role creator can never mint a role more powerful than their own access. ## Permission catalog ### team The organization profile itself. | Action | Description | | --- | --- | | update | Edit the organization name, slug, description, and avatar | ### members People already in the organization. | Action | Description | | --- | --- | | update | Change a member's roles | | delete | Remove members from the organization | ### invitations Bringing new people in. | Action | Description | | --- | --- | | create | Invite new members | | cancel | Cancel pending invitations | ### skills The organization's skills and their versions. | Action | Description | | --- | --- | | read | View private organization skills | | create | Create skills and publish new versions | | update | Edit skill metadata and visibility | | delete | Delete skills | | publish | Make skills public or unlisted while the organization restricts publishing to trusted roles | ### folders The folder tree that organizes skills. | Action | Description | | --- | --- | | create | Create folders | | update | Rename and move folders | | delete | Delete empty folders | | manageAcl | Restrict folders and manage folder access (also bypasses folder restrictions) | ### team API tokens Organization-scoped API tokens. | Action | Description | | --- | --- | | create | Create organization tokens | | delete | Revoke organization tokens | ### audit log The organization's activity history. | Action | Description | | --- | --- | | read | View the organization audit log | ### GitHub sync Mirroring skills to a GitHub repository. | Action | Description | | --- | --- | | read | View sync status | | manage | Connect, configure, and disconnect the repo | ### CI/CD (OIDC) Policies letting CI pipelines exchange OIDC tokens for access. | Action | Description | | --- | --- | | manage | Create, edit, and delete CI/CD policies | ### roles Custom roles and their permissions. | Action | Description | | --- | --- | | create | Create custom roles | | read | View role definitions | | update | Edit custom role permissions | | delete | Delete custom roles | ### teams Teams that group members and grant them attached roles. | Action | Description | | --- | --- | | create | Create teams | | update | Rename teams, attach roles, and manage team members | | delete | Delete teams | ## How permissions combine A member's effective permissions are the union of: - the roles assigned to them directly, and - the roles attached to every [team](https://localskills.sh/docs/teams) they belong to. Team-derived roles are live: changing a team's attached roles or a member's team membership updates their permissions immediately, everywhere permissions are checked. Restricted folders can narrow these permissions further for specific subtrees of the skill library — see [Folders & Access Control](https://localskills.sh/docs/folders). Role changes are captured in the audit log as `role.created`, `role.updated`, `role.deleted`, and `organization.member_role_updated`. --- # GitHub Sync Source: https://localskills.sh/docs/github-sync GitHub Sync mirrors your organization's entire skill library to a GitHub repository — folder structure included. Skills stay reviewable, diffable, and greppable in the tooling your team already uses. ## How it works Sync is **bidirectional**. Skill and folder changes made on the platform push to the repo automatically in the background. Pushes to the connected branch flow the other way: edits to `SKILL.md`, `RULE.md`, or package files import back as new skill versions, and a brand-new directory containing a `SKILL.md` creates a new private skill. Imported versions record their provenance (commit and author) in the version message and audit log. Deleting or moving a skill directory in the repo does **not** delete or move the skill — the next full sync restores it. When the same skill changes in both places between syncs, **GitHub wins**: both edits are kept in the version history, and the repo's content becomes the current version — even if the platform edit was saved after the push. After an import, the sync bot pushes one small commit refreshing each skill's `.localskills.json` version metadata. **Sync now** first imports any pending repo changes, then runs a full re-mirror — it is also the recovery path if a webhook delivery was ever missed. ## Connecting a repository From **Organization > GitHub Sync**, click **Connect GitHub repo**. You'll be sent through a GitHub App installation where you pick the account and repository; back on the settings page you confirm the repo and the first full sync runs. Connecting, disconnecting, and manual syncs require the `GitHub sync: manage` permission and are recorded in the audit log (`github_sync.connect`, `github_sync.disconnect`, `github_sync.manual_sync`). ## Repository layout Each skill becomes a directory under `skills/` following your organization's folder tree, and an auto-generated `skills/README.md` indexes every skill. Everything outside `skills/` — your own README, `.github/`, workflows, docs — is never written to or deleted: ``` skills/ README.md # auto-generated skill index engineering/ reviews/ code-review-checklist/ .localskills.json # id, version, semver, type, visibility, tags SKILL.md # text skill content (managed header) references/security.md # package skills: every file, verbatim scripts/diff-stats.sh marketing/ landing-page-copy/ .localskills.json RULE.md # rules use RULE.md instead of SKILL.md ``` Text skills are written as a single `SKILL.md` (or `RULE.md` for rules) with a managed header noting that edits sync back. Package skills are unpacked in full — every file in the package appears in the repo. The `.localskills.json` sidecar records the skill id, current version and semver, type, visibility, and tags. Renaming or moving a folder on the platform renames the corresponding paths in the repo on the next sync. ## Security considerations > **All** skills are mirrored — including private and unlisted ones, and skills in [restricted folders](https://localskills.sh/docs/folders). "Private" in the repo means readable by every current and future repo collaborator and GitHub org member — a set localskills.sh does not control. With bidirectional sync this cuts both ways: **repo write access is write access to every synced skill**, including those in restricted folders — repo pushes bypass folder access controls. Use a private repository, protect the connected branch if you want review before import, and manage repo access as carefully as you manage organization membership. Disconnecting removes the platform's access: the GitHub App installation is uninstalled once no other organization still uses it. The mirrored files remain in your repo. --- # CI/CD & OIDC Source: https://localskills.sh/docs/cicd Publish skills from CI pipelines without storing any long-lived secret. A trust policy tells localskills.sh which pipelines to trust; the pipeline exchanges its OpenID Connect identity token for a short-lived API token at run time. ## How it works GitHub Actions and GitLab CI can mint OIDC identity tokens that cryptographically prove which repository, branch, and environment a job is running for. localskills.sh verifies that token against your organization's **trust policies** and, on a match, issues an API token valid for **1 hour**. Nothing is stored in your CI secrets. Request the identity token with the audience `https://localskills.sh`. ## Trust policies Manage policies under **Organization > CI/CD** (requires the `CI/CD (OIDC): manage` permission). A policy matches on: | Field | Description | | --- | --- | | Provider | GitHub Actions or GitLab CI | | Repository | owner/repo for one repo, or owner/* for every repo in an org/group | | Ref filter | GitHub uses full refs (refs/heads/main); GitLab uses short refs (main); * matches any | | Environment filter | Optional — restrict to a deployment environment (e.g. production) | Policy changes and every token exchange are recorded in the audit log (`oidc.policy_created`, `oidc.policy_updated`, `oidc.policy_deleted`, `oidc.token_exchanged`). ## GitHub Actions ```yaml # .github/workflows/publish.yml permissions: id-token: write contents: read jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm install -g @localskills/cli - uses: actions/github-script@v7 id: oidc with: script: return await core.getIDToken('https://localskills.sh') result-encoding: string - run: | localskills login --oidc-token "${{ steps.oidc.outputs.result }}" --team your-org localskills push skill.md --skill my-skill --patch ``` The `id-token: write` permission is what lets the job mint an OIDC token. `--team` is your organization's slug. ## GitLab CI ```yaml # .gitlab-ci.yml publish: id_tokens: OIDC_TOKEN: aud: https://localskills.sh script: - npm install -g @localskills/cli - localskills login --oidc-token "$OIDC_TOKEN" --team your-org - localskills push skill.md --skill my-skill --patch ``` ## Publishing from the pipeline `localskills push` is the CI-friendly publish command — fully non-interactive, targeting an existing skill: ```bash $ localskills push ./skills/my-skill --skill my-skill --patch -m "$CI_COMMIT_TITLE" ``` It accepts a file or a skill folder, and bumps `--patch`, `--minor`, or `--major` — or pins an exact `--version`. See the `/docs/cli` reference for all flags. > Prefer OIDC where your CI supports it. If you must use a stored secret instead, use an organization API token (`lskt_…`) rather than a personal one — it survives the creator leaving and attributes actions to the organization. See `/docs/api-tokens`. --- # SSO / SAML 2.0 Source: https://localskills.sh/docs/sso Enterprise organizations can configure SAML 2.0 SSO for centralized authentication. ## Overview localskills.sh acts as a SAML Service Provider (SP). It supports any SAML 2.0–compliant identity provider, including Okta, Azure AD, Google Workspace, and OneLogin. Configuring SSO requires the **organization owner** role. Configuration lives under **Organization > SSO & SCIM**. ## SP configuration Provide the following values to your identity provider. Replace `{providerId}` with the provider ID from the SSO settings page. **Entity ID** (also the SP metadata URL) ``` https://localskills.sh/api/auth/sso/saml2/sp/metadata?providerId={providerId} ``` **ACS URL** ``` https://localskills.sh/api/auth/sso/saml2/sp/acs/{providerId} ``` The ACS URL doubles as the SAML callback — if your identity provider asks for a separate callback or reply URL, use the ACS URL. The SSO settings page shows both values for your organization with copy buttons. ## IdP setup Configure an SSO provider from your organization's **SSO & SCIM** tab. Pick your identity provider — pre-built templates cover **Okta**, **Cloudflare Access**, and **Authentik**, with a generic template for any other SAML 2.0 IdP — and follow that provider's guide below to create the app on the IdP side. Each template presets the right attribute mapping and groups attribute for that provider. Then connect it by pasting the IdP's **metadata XML** (recommended — the issuer, sign-in URL, and signing certificate are extracted automatically), or enter the SSO URL, issuer, and certificate manually. Finish with one or more email domains. The provider ID embedded in the SP URLs is assigned automatically and never changes. For exotic setups (encrypted assertions, signed requests, custom attribute mapping) the **JSON (advanced)** tab still accepts the full Better Auth SAML configuration. ## Okta Copy your organization's **Entity ID** and **ACS URL** from the SSO settings page — they embed your provider ID, unlike the `{providerId}` placeholders shown above. 1. In the Okta Admin Console, go to Applications → Applications → Create App Integration and pick SAML 2.0. 2. Set Single sign-on URL to your ACS URL and Audience URI (SP Entity ID) to your Entity ID. 3. Set Name ID format to EmailAddress and Application username to Email. 4. Add attribute statements: email → user.email and displayName → user.displayName. For group → team mapping, add a group attribute statement named groups (filter: Matches regex .*). 5. Finish the wizard and assign people or groups to the app. **Metadata XML:** Open the app's Sign On tab, copy the Metadata URL, and open it in your browser to get the XML. ## Cloudflare Access Copy your organization's **Entity ID** and **ACS URL** from the SSO settings page — they embed your provider ID, unlike the `{providerId}` placeholders shown above. 1. In the Cloudflare dashboard, go to Zero Trust → Access controls → Applications → Create new application and pick SaaS application. 2. Enter a custom application name, then select SAML. 3. Set Entity ID to your Entity ID and Assertion Consumer Service URL to your ACS URL. Set Name ID Format to Email. 4. Save the application and add an Access policy for who may sign in. 5. Access sends the email, name, and groups attributes automatically — no attribute statements needed. **Metadata XML:** After saving, Cloudflare shows the SSO endpoint — fetch /saml-metadata in your browser to get the XML. (Manual values: SSO endpoint, Access Entity ID, and Public key.) ## Authentik Copy your organization's **Entity ID** and **ACS URL** from the SSO settings page — they embed your provider ID, unlike the `{providerId}` placeholders shown above. 1. In the authentik admin interface, go to Applications → Providers → Create and pick SAML Provider. 2. Set ACS URL to your ACS URL and Audience to your Entity ID. Keep Service Provider Binding set to Post. 3. Under Advanced protocol settings, keep the default property mappings (this template maps authentik's default attribute names) and select a signing certificate. 4. Create an Application (Applications → Applications → Create) and set its Provider to the one you just made. **Metadata XML:** On the provider's page, use the Metadata section to download or copy the metadata XML (also served at /application/saml//metadata/). ## Custom SAML Copy your organization's **Entity ID** and **ACS URL** from the SSO settings page — they embed your provider ID, unlike the `{providerId}` placeholders shown above. 1. Create a SAML 2.0 application in your identity provider. 2. Set the assertion consumer service (ACS, sometimes called reply or callback URL) to your ACS URL and the SP entity ID (audience) to your Entity ID. 3. Send the user's email in an attribute named email and their display name in displayName — or adjust the attribute mapping in the JSON (advanced) tab. 4. For group → team mapping, send group membership in an attribute named groups. **Metadata XML:** Most IdPs offer a metadata XML download or URL for the application. If yours doesn't, switch to Manual and enter the SSO URL, issuer, and signing certificate. ## Email domains Whitelist one or more email domains for SSO (e.g. `example.com`). Only users with matching email domains can sign in via SAML. Each domain must be **verified** before sign-in routes to SSO: add the TXT record shown on the SSO settings page with the record name set to the claimed domain (host `@` for a zone apex), then click Check Now. Unverified domains never trigger SSO auto-detection. Multiple domains are supported; verify each one. A domain can only be verified by one organization at a time. ## Sign-in flow Users start SSO by entering an email address on the sign-in page. When the domain matches a configured provider with a verified domain, localskills.sh redirects them to your identity provider. First-time SSO users are provisioned into the organization automatically (audit events `sso.user_created` and `sso.login_success`). ## Group → team mapping Map identity-provider groups to localskills.sh teams from the **SSO & SCIM** tab. On every SSO login, the member's team membership is reconciled from the IdP's groups attribute: they are added to teams whose mapped group they hold and removed from mapped teams whose group is gone. Teams without a mapping are never touched. Because teams carry roles, this is how IdP group membership translates into permissions — see `/docs/teams` and `/docs/roles`. Syncs are audited as `sso.team_sync` and mapping changes as `sso.group_mappings_updated`. > All SSO configuration changes are tracked in the organization audit log. --- # SCIM 2.0 Source: https://localskills.sh/docs/scim SCIM 2.0 directory sync enables automatic user and group provisioning from your identity provider. ## Overview localskills.sh implements the SCIM 2.0 protocol for automated user lifecycle management through Better Auth. It works with Okta, Azure AD, OneLogin, and any other SCIM 2.0–compliant identity provider. Configuring SCIM requires the **organization owner** role. Configuration lives under **Organization > SSO & SCIM**. **SCIM base URL** ``` https://localskills.sh/api/auth/scim/v2 ``` ## Token management Generate a SCIM provider token from your organization's **SSO & SCIM** tab. Tokens are scoped to the selected provider ID and displayed only once at creation time. Configure the token as Bearer authentication in your identity provider's SCIM integration — the per-provider guides below show where (Okta, Authentik, or any generic SCIM 2.0 client). ## Okta 1. In the app's General tab, set Provisioning to SCIM. 2. In the Provisioning tab, set the SCIM connector base URL to the SCIM base URL above and the unique identifier field to email. 3. Set Authentication Mode to HTTP Header and paste a token generated from the SSO & SCIM settings page. 4. Under Provisioning → To App, enable Create Users, Update User Attributes, and Deactivate Users. ## Cloudflare Access Cloudflare Access does not push SCIM provisioning to service providers — its SCIM support pulls users from your IdP into Access. Users are provisioned here automatically on their first SSO sign-in instead. ## Authentik 1. In authentik, go to Applications → Providers → Create and pick SCIM Provider. 2. Set URL to the SCIM base URL above and Token to a token generated from the SSO & SCIM settings page. 3. Edit your application and add the SCIM provider under Backchannel Providers. ## Custom SAML 1. Point your identity provider's SCIM 2.0 client at the SCIM base URL above. 2. Authenticate with a Bearer token generated from the SSO & SCIM settings page. ## User provisioning Your identity provider pushes create, update, and delete events to the SCIM endpoint. Deactivated users are automatically removed from the organization; provisioning and deprovisioning are recorded in the audit log (`scim.user_provisioned`, `scim.user_updated`, `scim.user_deprovisioned`). **Supported SCIM attributes:** - `userName` — email address - `name.formatted` — display name - `externalId` — IdP identifier - `active` — account status ## Group provisioning Your identity provider can push SCIM groups to localskills.sh. Group records and membership changes are handled through the shared SCIM endpoint. ## Roles & permissions SCIM manages membership, not permissions. Assign roles to provisioned users in the dashboard — directly or via teams — and use SSO group → team mapping to drive permissions from your directory groups. See `/docs/roles` and `/docs/teams`. --- # API Tokens Source: https://localskills.sh/docs/api-tokens API tokens provide programmatic access to the localskills.sh API and power CLI authentication in headless environments. There are two kinds: personal tokens that act as you, and organization tokens that act as the organization. ## Personal tokens (lsk_) Create personal tokens from your account menu (bottom of the dashboard sidebar) under **Settings > Personal API Tokens**. They authenticate as you, with your roles and folder access, and use the `lsk_` prefix. Each user can hold up to 25 tokens. ## Organization tokens (lskt_) Organization tokens live under **Organization > Tokens** and use the `lskt_` prefix. They authenticate as the organization — not any person — so they keep working after the creator leaves, and actions are attributed to the token in the audit log. Creating and revoking them requires the `team API tokens` permission (built into owner and admin). An optional expiry (in days) can be set at creation. > Organization tokens minted *without* restrictions bypass folder restrictions — they act as the organization itself. Treat them like deploy keys: scope them to automation, set an expiry, and revoke them when unused. To confine one, add [fine-grained permissions](https://localskills.sh/docs/api-tokens#fine-grained) at creation. For CI, prefer [OIDC token exchange](https://localskills.sh/docs/cicd) — it needs no stored secret at all. ## Format & storage Both kinds are the prefix followed by 64 hexadecimal characters (256 bits of entropy). The full token is displayed only once at creation time — the server stores a SHA-256 hash. Each token shows a **last used** timestamp so you can spot stale credentials. ## Using a token For CLI authentication in headless environments: ```bash $ localskills login --token lsk_your_token_here ``` For direct API access, pass it as a Bearer token: ```bash $ curl -H "Authorization: Bearer lskt_your_token_here" \ https://localskills.sh/api/skills ``` ## Fine-grained permissions Both token types can optionally be restricted at creation time (choose **Custom** under Permissions in the create dialog, or pass the fields below to the API). A token with no restrictions behaves exactly as before. Restrictions are immutable — revoke and re-mint to change them. **Scopes** set a permission ceiling. Scopable permissions: `skill: read, create, update, delete`, `folder: create, update, delete`, `auditLog: read`, `githubSync: read, manage`. Effective access is the intersection of the holder's real permissions and the token's scopes — scopes can narrow access but never widen it. A scoped token can never manage members, roles, folder ACLs, or mint tokens. **Folder narrowing** confines a token to one or more folder subtrees. A narrowed token can only read or write skills (and folders) inside those subtrees; root-level skills are out of reach. Public and unlisted skills outside restricted folders remain readable — a token never sees less than an anonymous request. ```bash $ curl -X POST https://localskills.sh/api/user/tokens \ -H "Content-Type: application/json" \ --cookie "$SESSION" \ -d '{"name":"ci-readonly","expiresInDays":90, "scopes":{"skill":["read"]}, "allowedFolderIds":["fld_abc123"]}' ``` > An organization token minted *without* restrictions keeps the documented legacy behavior: it acts with the admin permission map and bypasses folder ACLs. Add scopes or folder narrowing to confine it. ## Revoking Revoke personal tokens from **Settings** and organization tokens from **Organization > Tokens**. Revoked tokens are invalidated immediately. Token lifecycle events appear in the audit log (`user.token_created`, `user.token_deleted`, `organization.token_created`, `organization.token_revoked`). --- # Analytics Source: https://localskills.sh/docs/analytics Every skill has built-in download analytics, visible on the skill's **Analytics** tab in the dashboard. **Metrics** — Total downloads, downloads in the last 7 days, and downloads in the last 30 days. A daily breakdown chart shows trends over time. **Sources** — Downloads are tracked by source: CLI, web, or API. **Privacy** — IP addresses are anonymized using HMAC-SHA256 hashing. Original IP addresses are never stored. --- # Audit Log Source: https://localskills.sh/docs/audit-log localskills.sh maintains audit logs at both user and organization level to provide visibility into all platform activity. ## Organization audit log The organization log lives under **Organization > Audit Log** and requires a role with the audit log read permission (built into `owner` and `admin`). It aggregates all member activity: skill and folder changes, invitations, role and team changes, SSO and SCIM events, GitHub Sync, and OIDC token exchanges. Entries are filterable by action type. ## Your account scope The same page has a **Your account** scope, visible to every member, showing activity performed by your own account — skill publishes, token management, profile updates, and CLI logins. ## Events tracked | Event | Description | | --- | --- | | `skill.created` | New skill published (web, CLI, or MCP) | | `skill.updated` | Skill settings changed | | `skill.version_created` | New version pushed (web, CLI, or MCP) | | `skill.version_imported` | New version imported from a GitHub push | | `skill.created_via_import` | New skill created from a GitHub push (new repo directory) | | `skill.deleted` | Skill deleted | | `skill.version_reverted` | Version rollback | | `folder.created` | Folder created | | `folder.updated` | Folder renamed or moved | | `folder.deleted` | Folder deleted | | `folder.acl_updated` | Folder restriction or access grants changed | | `personal_workspace.provisioning_scheduled` | Member workspace provisioning scheduled | | `personal_workspace.provisioned` | Member workspace provisioned | | `personal_workspace.enabled` | Personal Workspaces enabled | | `personal_workspace.disabled` | Personal Workspaces disabled | | `personal_workspace.backfill_scheduled` | Existing-member backfill scheduled | | `personal_workspace.retry_scheduled` | Failed provisioning retry scheduled | | `personal_workspace.namespace_updated` | Managed namespace renamed or moved | | `personal_workspace.repaired` | Managed workspace access repaired | | `user.token_created` | API token created | | `user.token_deleted` | API token revoked | | `user.profile_updated` | Profile changes | | `user.cli_login` | CLI device code login | | `user.anonymous_registered` | Anonymous CLI credential registered | | `organization.created` | Organization created | | `organization.updated` | Organization settings changed | | `organization.avatar_uploaded` | Organization avatar uploaded | | `organization.avatar_deleted` | Organization avatar removed | | `organization.member_invited` | Member invited to organization | | `organization.member_joined` | Member accepted invitation | | `organization.auto_join_updated` | Domain auto-join enabled or disabled | | `organization.auto_joined` | Member joined automatically through a matching email domain | | `organization.auto_join_invitation_created` | Invitation auto-created for a matching email domain (before auto-join created memberships directly) | | `organization.member_role_updated` | Member role changed | | `organization.token_created` | Organization API token created | | `organization.token_revoked` | Organization API token revoked | | `team.created` | Team created | | `team.updated` | Team renamed or roles changed | | `team.deleted` | Team deleted | | `team.member_added` | Member added to a team | | `team.member_removed` | Member removed from a team | | `role.created` | Custom role created | | `role.updated` | Custom role permissions changed | | `role.deleted` | Custom role deleted | | `sso.provider_created` | SSO provider configured | | `sso.provider_updated` | SSO settings modified | | `sso.provider_deleted` | SSO provider removed | | `sso.login_success` | Successful SSO login | | `sso.user_created` | User provisioned via SSO | | `sso.domain_claimed` | Domain verification started | | `sso.domain_verified` | Domain ownership verified | | `sso.domain_removed` | Domain claim removed | | `sso.group_mappings_updated` | SSO group→team mappings changed | | `sso.team_sync` | Member's teams reconciled from IdP groups | | `scim.provider_created` | SCIM provisioning enabled | | `scim.provider_deleted` | SCIM provisioning disabled | | `scim.user_provisioned` | User provisioned via SCIM | | `scim.user_updated` | User updated via SCIM | | `scim.user_deprovisioned` | User removed via SCIM | | `oidc.policy_created` | OIDC trust policy created | | `oidc.policy_updated` | OIDC trust policy updated | | `oidc.policy_deleted` | OIDC trust policy deleted | | `oidc.token_exchanged` | CI token exchanged via OIDC | | `github_sync.connect` | GitHub repo sync connected | | `github_sync.disconnect` | GitHub repo sync disconnected | | `github_sync.manual_sync` | Manual GitHub sync triggered | ## Retention Audit log entries are retained for **90 days** and auto-purged after that period. Logs are paginated and filterable by action type. --- # API Reference Source: https://localskills.sh/docs/api-reference The localskills.sh REST API powers all platform functionality. Full interactive documentation is available at [/api-docs](https://localskills.sh/api-docs). ## Authentication All API endpoints accept two authentication methods: **Session cookie** — Automatically included when signed in via the browser. Managed by Better Auth. **Bearer token** — Pass an API token in the `Authorization` header: ``` Authorization: Bearer lsk_your_token_here ``` All endpoints accept either method. Bearer tokens are recommended for programmatic access, CI/CD pipelines, and CLI usage. Personal tokens start with `lsk_`; organization tokens start with `lskt_`. ## Endpoints overview - **Skills** — Create, list, update, and delete skills. Manage versions (publish, revert), download content and packages, and view analytics. - **Folders** — Manage each organization's folder tree and per-folder access restrictions. - **Organizations** — Create and manage organizations, invite members, assign roles, manage teams and custom roles, and upload avatars. - **Organization tokens & OIDC** — Issue and revoke organization API tokens, manage CI/CD trust policies, and exchange pipeline OIDC tokens for short-lived access. - **SSO** — Configure SAML 2.0 connections, retrieve SP metadata, and manage identity provider settings and domain verification. - **SCIM** — SCIM 2.0 endpoints for automated user and group provisioning, provider configuration, schemas, and resource types. - **User** — Manage your profile, create and revoke API tokens, and view your audit log. - **CLI Auth** — Device code flow for CLI authentication. The CLI initiates a device code request, the user approves in the browser, and the CLI polls for completion. For full endpoint details, request and response schemas, and interactive testing, visit the [interactive API documentation](https://localskills.sh/api-docs). --- # Security Source: https://localskills.sh/docs/security ## Rate limiting API routes are rate-limited to 60 requests per minute per IP via Cloudflare. Credential and email-sending auth endpoints use a stricter limit of 10 requests per minute. SCIM endpoints are keyed by Bearer token instead of IP, so identity-provider directory syncs don't compete with other traffic from shared egress IPs. Exceeding a limit returns `429`. ## Security headers | Header | Value | | --- | --- | | X-Frame-Options | DENY | | X-Content-Type-Options | nosniff | | Strict-Transport-Security | max-age=63072000; includeSubDomains | | Referrer-Policy | strict-origin-when-cross-origin | | X-DNS-Prefetch-Control | off | ## Authentication security API tokens are generated with 256-bit entropy and stored as SHA-256 hashes — the plaintext is shown once and never persisted. SCIM tokens are likewise stored hashed. CLI device codes expire after 10 minutes. CI/CD OIDC tokens are verified against the provider's published signing keys (issuer, audience, and expiry checked) and exchanged tokens live for 1 hour. ## Content limits | Resource | Limit | | --- | --- | | Skill content (text) | 512 KB | | Organization description / user bio | 10 KB | | Package skill (compressed) | 100 MB | | Package skill (uncompressed) | 100 MB | | Package skill file count | 500 files | | Skill name | 1–100 chars | | Organization name | 2–50 chars | | Username | 2–39 chars | | Custom role name | 2–40 chars | | API tokens per user | 25 | | Anonymous shared skills | 10 per identity |