# localskills.sh docs > Documentation for localskills.sh — create, share, and install agent skills and rules for Cursor, Claude Code, Windsurf, and other AI tools. Index: https://docs.localskills.sh/llms.txt # Documentation > Everything you need to create, share, and install agent skills. CLI reference, teams, SSO, SCIM, API tokens, and more. Source: https://docs.localskills.sh/ · Markdown: https://docs.localskills.sh/index.md Everything you need to create, share, and install agent skills. - [Getting Started](/getting-started) — Install the CLI, authenticate, and install your first skill in minutes. ## Skills & Library - [Skills](/skills) — Types, text and package formats, visibility, versioning, supported platforms, and install methods. - [Folders & Access Control](/folders) — Organize skills into a folder tree and restrict folders to specific teams and members. - [Personal Workspaces](/personal-workspaces) — Automatically provision a managed private skill workspace for every organization member. - [Anonymous Sharing](/sharing) — Share a skill without an account: a pastebin for agent skills, keyed to a local Ed25519 identity. ## Developer Tools - [CLI Reference](/cli) — Complete reference for the localskills CLI: install, publish, pull, uninstall, and list commands. - [CLI JSON Output](/cli-json) — Machine-readable CLI output: typed payloads, structured error codes, and strict non-interactive behavior with -o json. - [MCP Server](/mcp) — Connect AI agents directly via the Model Context Protocol. Setup for Claude Code, claude.ai, Cursor, Codex CLI, Windsurf, VS Code, and more. - [Managed MCP Servers](/mcp-servers) — Manage your team's MCP servers centrally and install them into any tool with one CLI command. ## Teams & Administration - [Organizations & Teams](/teams) — Create an organization, invite members, and group them into role-carrying teams. - [Roles & Permissions](/roles) — Built-in roles plus custom roles with granular permissions across skills, folders, and settings. - [Analytics](/analytics) — Track skill downloads by source with privacy-preserving analytics and daily trend breakdowns. - [Audit Log](/audit-log) — User and organization audit logs with 90-day retention covering all platform actions. ## Automation & API - [GitHub Sync](/github-sync) — Mirror your organization's skill library to a GitHub repository, folder structure included. - [CI/CD & OIDC](/cicd) — Publish from GitHub Actions and GitLab CI with OIDC token exchange, no stored secrets. - [API Tokens](/api-tokens) — Generate and manage API tokens for CI/CD pipelines, headless CLI auth, and programmatic access. - [API Reference](/api-reference) — REST API authentication, endpoints for skills, teams, SSO, SCIM, and CLI auth. ## Enterprise & Security - [SSO / SAML 2.0](/sso) — Configure SAML 2.0 Single Sign-On with Okta, Azure AD, Google Workspace, and other identity providers. - [SCIM 2.0](/scim) — Automate user and group provisioning with SCIM 2.0 directory sync from your identity provider. - [Security](/security) — Rate limiting, security headers, authentication security, and content limits. ## Frequently Asked Questions How do I install the localskills CLI? Install the localskills CLI globally via npm: `npm i -g @localskills/cli` (or use pnpm, yarn, or bun: `bun add -g @localskills/cli`). Then verify the installation with: `localskills --version`. What AI coding tools does localskills support? localskills supports 8 AI coding tools: Cursor, Claude Code, Codex CLI, Windsurf, Cline, GitHub Copilot, OpenCode, and Aider. Skills can be installed into any of these tools with a single CLI command. How do I install a skill with localskills? Run `localskills install ` to install a skill. The CLI will walk you through platform selection, scope, and install method. You can skip prompts with flags: `localskills install my-skill --target cursor claude --project --symlink`. What is the difference between a skill and a rule? 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 like Claude Code, rules are placed in a separate `.claude/rules/` directory. How do I publish a skill to localskills.sh? Run `localskills publish [path]` with a file or a whole skill folder (published as a multi-file package). Omit the path to scan for unpublished skills across all platform directories. You can set visibility to public, private, or unlisted. Each update creates a new version you can roll back. Can I restrict which team members see a skill? Yes. Organize skills into folders and restrict a folder to specific teams or members with viewer, editor, or manager grants. Restricted folders are hidden from everyone else across the dashboard, API, CLI, and MCP server. Can CI pipelines publish skills without stored secrets? Yes. Configure an OIDC trust policy for your GitHub Actions or GitLab CI pipeline, then exchange the pipeline's identity token with `localskills login --oidc-token` and publish with `localskills push`. No long-lived secret is stored in CI. Does localskills support SSO and SCIM? Yes. localskills.sh supports SAML 2.0 SSO with any compliant identity provider (Okta, Azure AD, Google Workspace, etc.) and SCIM 2.0 for automated user and group provisioning. SSO configuration requires the organization owner role. # Analytics > Track agent skill downloads by source with privacy-preserving analytics on localskills.sh. Daily trends, 7-day and 30-day breakdowns. Source: https://docs.localskills.sh/analytics/ · Markdown: https://docs.localskills.sh/analytics/index.md Every skill has built-in download analytics, visible on the skill's **Analytics** tab in the dashboard. **Metrics**: Total downloads plus 7- and 30-day counts, with a daily breakdown chart showing trends over time. **Sources**: Downloads are tracked by source (CLI, web, or API). **Privacy**: IP addresses are anonymized with HMAC-SHA256 hashing; originals are never stored. **Installs**: The skill's **Installs** tab shows which team members have the skill installed and at which version, with a per-version breakdown. MCP servers get the same view on the MCP Servers page. Install state is reported by the CLI on `install`, `pull`, and `uninstall`, only for signed-in members of the skill's own team; anonymous and cross-team installs are never recorded. # API Reference > REST API documentation for localskills.sh. Authentication methods, endpoint overview for skills, teams, SSO, SCIM, and CLI auth. Source: https://docs.localskills.sh/api-reference/ · Markdown: https://docs.localskills.sh/api-reference/index.md 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**: included automatically when signed in via the browser. Managed by Better Auth. **Bearer token**: pass an API token in the `Authorization` header: ```text Authorization: Bearer lsk_your_token_here ``` 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 requests a device code, 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). # API Tokens > Personal and organization API tokens for localskills.sh: CLI authentication, CI/CD pipelines, and programmatic access. Source: https://docs.localskills.sh/api-tokens/ · Markdown: https://docs.localskills.sh/api-tokens/index.md 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. > **Note** > > 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](#fine-grained-permissions) at creation. For CI, > prefer [OIDC token exchange](/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; 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: ```sh localskills login --token lsk_your_token_here ``` For direct API access, pass it as a Bearer token: ```sh 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. ```sh 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"]}' ``` > **Note** > > 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`). # Audit Log > User and organization audit logs on localskills.sh. Track skill publishes, SSO logins, SCIM provisioning, token management, and more with 90-day retention. Source: https://docs.localskills.sh/audit-log/ · Markdown: https://docs.localskills.sh/audit-log/index.md 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 your own account's activity: 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 | | `mcp_server.created` | MCP server registered | | `mcp_server.imported` | MCP server imported from the localskills catalog | | `mcp_server.catalog_updated` | Unedited catalog import auto-updated to a new catalog definition | | `mcp_server.updated` | MCP server configuration changed | | `mcp_server.deleted` | MCP server removed | | `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 | | `mfa.enabled` | Two-factor authentication enabled | | `mfa.disabled` | Two-factor authentication disabled | | `mfa.passkey_added` | Passkey registered | | `mfa.passkey_removed` | Passkey removed | | `mfa.org_requirement_updated` | Organization MFA requirement changed | | `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. # CI/CD & OIDC > Publish skills from GitHub Actions and GitLab CI without stored secrets: OIDC trust policies exchange your pipeline's identity token for short-lived access. Source: https://docs.localskills.sh/cicd/ · Markdown: https://docs.localskills.sh/cicd/index.md 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: | Flag | 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; restricts 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 and targeting an existing skill: ```sh 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`; `--version` pins an exact semver. See the [CLI Reference](/cli) for all flags. > **Note** > > 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 [API Tokens](/api-tokens). # CLI JSON Output > Machine-readable output for the localskills CLI: pass -o json to any command for typed payloads on stdout, structured error codes on stderr, and strict non-interactive behavior. Source: https://docs.localskills.sh/cli-json/ · Markdown: https://docs.localskills.sh/cli-json/index.md Every `localskills` command accepts `-o json` (CLI 0.20.0+) for machine-readable output. Use it from scripts, CI, and agent harnesses instead of parsing human text. ## The contract With `-o json`, stdout carries exactly one JSON document (the command's data, no envelope) and stderr carries at most one error object. Spinners, progress lines, tips, and the update-check notice are suppressed. ```sh localskills list -o json ``` ```json [ { "publicId": "hP5jnYv6rT", "slug": "api-design-guide", "name": "API Design Guide", "description": "How to design APIs", "type": "skill", "visibility": "private", "tags": ["api", "design"], "currentVersion": 3, "currentSemver": "1.2.0", "tenantId": "d298c335-…", "folderId": null, "createdBy": "7f3a91c2-…", "updatedAt": "2026-07-10T00:00:00.000Z" } ] ``` ```sh localskills push ./skill -s nope -o json # stderr, exit 1: ``` ```json { "error": { "code": "E_API", "message": "Skill not found", "details": { "httpStatus": 404, "serverMessage": "Skill not found" } } } ``` Exit codes follow one rule: | Flag | Description | | --- | --- | | exit 0 | Clean success (benign per-target skips allowed; they appear in the payload's skipped array) | | exit 1 + stdout payload | Partial failure: the operation completed for some targets; failures are recorded in the payload | | exit 1 + stderr error | Hard failure: nothing usable happened; parse the error object | `-o json` is strictly non-interactive: anything that would prompt (pickers, confirmations, secret input) either takes its answer from a flag or fails fast with `E_INTERACTION_REQUIRED` naming what was needed. Commands that would scan or browse interactively require their positional argument (`install`, `uninstall`, `publish`, `share`); `install` also requires `--target` plus `--global` or `--project`; `profile delete` requires `--force`; `mcp install` requires every member-supplied value via `--var NAME=value`. **Behavior change (Connectors unification):** `mcp install` of a remote http server now ALSO requires `--via ` in `-o json` mode. The old implicit local install fails with `E_INTERACTION_REQUIRED` naming the flag; pass `--via local` to restore the previous behavior verbatim. stdio servers need no flag and install exactly as before, but passing `--via central` for one is rejected with `E_INVALID_ARGUMENT` (only remote http servers can run through MCP Central). > **Note** > > One exception to the single-document rule: `login -o json` (device flow) > emits compact NDJSON: first a > `{"status":"pending","verificationUrl":…,"userCode":…}` line so your caller > can surface the code, then a final > `{"status":"authenticated","user":{…}}` line after browser approval. ## Per-command payloads Payloads are declared as TypeScript interfaces in the CLI source (`src/lib/json-payloads.ts`) and mapped field-by-field from API responses. Arrays are always present (empty, never omitted); optional fields are omitted when unknown; timestamps are ISO-8601. **Auth** ```ts // whoami { username, name, email, memberships: [{ tenantId, slug, name }], pendingInvitations: [{ id, organizationId, organizationName, organizationSlug, expiresAt }] } // login --token / --oidc-token { method: "token" | "oidc", user?, expiresAt? } // logout { loggedOut: true } ``` **Read** ```ts // list (bare array) [{ publicId, slug, name, description, type, visibility, tags, currentVersion, currentSemver, tenantId, folderId, createdBy, updatedAt }] // list --public (bare array) [{ publicId, slug, name, description, type, tags, currentVersion, currentSemver, author: { name, username }, downloads }] // folders list (bare array) [{ id, parentId, name, slug, path, skillCount }] // mcp list (bare array; registry rows via: "local", MCP Central // connector rows via: "central" with a `central` status block) [{ publicId, slug, tenantSlug, transport, command, url, revision, installed: { targets, updateAvailable } | null, via?: "local" | "central", central?: { connectionStatus, needsReconnect } }] // profile list (bare array) [{ name, active, activeViaOverride, isDefault, authenticated, skillCount }] ``` **Publish** ```ts // publish: action says whether a new skill was created or an // existing one got a new version (server-side slug upsert). // --create / --update pin the outcome instead of upserting: the // mismatch cases fail with E_ALREADY_EXISTS (details.existing), // E_NOT_FOUND, or E_AMBIGUOUS (details.candidates lists the // same-name skills with publicId/slug/folderId/path). { action: "created" | "updated", skill: { publicId, slug, name, type, visibility, currentVersion, currentSemver, tenantId, folderId }, url, format, fileCount?, ignoredFlags: ["folder" | "visibility"], // upsert kept server values warnings: [] } // push { skillRef, version: { id, skillId, version, semver, message, format?, fileCount?, createdAt } } // share { skill: { publicId, slug, name }, url, installCommand, identity: { username, tenantSlug, anonymous } } ``` **Local effects** ```ts // install { skill: { publicId, slug, name, type, visibility, currentVersion, currentSemver }, version, semver, requestedRange, format, installName, installed: [{ platform, scope, method, path, projectDir?, format, installedAt }], skipped: [{ platform, reason }], requiredMcpServers: [{ tenantSlug, slug, installed }] } // uninstall { skill: { installName, publicId }, removed: [installation…], failed: [{ platform, path, error }], purged } // pull { skills: [{ publicId, slug, outcome: "updated" | "upToDate" | "repaired" | "partial" | "failed", fromVersion?, toVersion?, toSemver?, warnings }], summary: { updated, upToDate, failed } } // move { skill: { id, slug, name, tenantId }, from: { folderId }, to: { folderId, path }, moved } // folders create { path, created: [paths…], alreadyExisted } // profile create / switch / delete { name, created: true } / { name, switched: true } / { name, deleted: true } // mcp install (server is absent only for `mcp install central`, // the endpoint-only invocation; connector/connectUrl appear on // via: "central" installs — the upstream connection completes in // the browser at connectUrl) { server?: { publicId, slug, tenantSlug, revision }, installed: [{ platform, scope, path, key, projectDir?, installedAt }], skipped: [{ platform, reason }], via?: "local" | "central", connector?: { publicId, slug, tenantId, connectionStatus, created }, connectUrl? } // mcp uninstall // mcp pull { removed, warnings } { updated, upToDate, warnings } ``` ## Error codes Errors are `{"error": {"code", "message", "details?"}}` on stderr with a non-zero exit. The code set is closed; `details` carries specificity such as `kind` (`skill | installedSkill | folder | path | profile | mcpServer | content | team`), `ref`, `flag`, or `httpStatus`. | Flag | Description | | --- | --- | | E_NOT_AUTHENTICATED | No valid login; run localskills login (also covers HTTP 401) | | E_INTERACTION_REQUIRED | A prompt was reached in -o json mode; details.flag names the flag that supplies the answer | | E_INVALID_ARGUMENT | Bad flag value or combination; details.flag names the offender | | E_NOT_FOUND | Skill, installed skill, folder, path, profile, MCP ref, team, or content not found (see details.kind/ref); also HTTP 404 responses (details.httpStatus) | | E_ALREADY_EXISTS | Resource already exists (profile create collision, publish --create against a taken name — details.existing names it); also HTTP 409 responses | | E_AMBIGUOUS | A reference matched more than one resource (MCP refs, publish --update with same-name skills in several folders — details.candidates lists them) | | E_FORBIDDEN | Access denied (HTTP 403, private skills) | | E_API | The server rejected the operation; details carries httpStatus and serverMessage | | E_NETWORK | Transport failure: offline, DNS, TLS, or a failed download | | E_PACK_FAILED | Folder packaging failed validation (size/count/SKILL.md rules) | | E_UPLOAD_FAILED | Staged package upload failed | | E_AUTH_DENIED | Device-flow login denied in the browser | | E_AUTH_EXPIRED | Device code expired or was already used | | E_NO_TARGETS | Install ran but nothing was installed; details.skipped explains each platform | | E_VERSION_CHANGED | `pull --frozen` found a skill that would move; details carries skill, from and to | | E_INTERNAL | Unexpected CLI failure (crash-level); message carries the underlying error | ## Stability policy The JSON surface evolves additively only. Removing or renaming a field, changing a type, or changing exit-code semantics requires a CLI major version. The error-code set is closed within a major version; new codes only appear in a major release. New payload fields and new `details.kind` values may appear in minor releases, so parse defensively and ignore what you don't know. # CLI Reference > Complete CLI reference for the localskills command-line tool. Install, publish, push, share, and organize agent skills from your terminal. Source: https://docs.localskills.sh/cli/ · Markdown: https://docs.localskills.sh/cli/index.md The `localskills` CLI (`@localskills/cli` on npm) installs, publishes, and organizes skills across AI coding tools from your terminal. ## login ```sh 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 [CI/CD & OIDC](/cicd) for the OIDC exchange, which lets GitHub Actions and GitLab CI publish without storing any secret. ## logout ```sh localskills logout ``` Clear the stored auth token from your local configuration. ## whoami ```sh localskills whoami ``` Display the current authenticated user's name, username, and email. ## install ```sh 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`. Full semver ranges are accepted (`~1.2.0`, `>=1.0.0`, `1.2.x`, `*`). The pin is stored and re-resolved by `pull`. Ranges skip prereleases unless the range is itself anchored to one on the same `major.minor.patch`, so `^1.0.0` will not pick up `1.1.0-beta.1`; request a prerelease by its exact version. | 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 (text-install default on macOS/Linux; also the Claude Code package default) | | `--copy` | Copy content directly into the target location (default on Windows, and for package installs on non-Claude tools) | > **Note** > > For single-file text skills, GitHub Copilot and Codex CLI use the *section* > install method (embedded markers in a shared file), and Windsurf global > installs also use sections. Multi-file package skills instead install as > folder-based Agent Skills on every supported tool except aider, copied into > the tool's native skills directory by default (use `--symlink` to link to the > local cache). If the skill declares [required MCP servers](/mcp-servers#linking-servers-to-skills) you don't have installed for that location and tool (user-level installs count everywhere for their tool, project installs per project, and each targeted tool needs its own entry), the CLI offers to install each one right after the skill. Requirements exist only on private team skills, so this only ever happens for members; in CI and other non-interactive runs the CLI prints the matching `localskills mcp install` commands instead of prompting. Declining a server never affects the skill install. ## uninstall ```sh 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 ```sh localskills list [--public | --installed | --personal] [--tag ] [--search ] ``` List skills available to you from your organizations. Pass `--public` to browse public skills instead; `--tag` and `--search` filter the public catalog. Pass `--personal` to list only skills in your managed personal workspace folders. `--installed` reads the local config instead of the API and shows what you have installed, each with its version pin (`latest` when unpinned) and target count. ## versions ```sh localskills versions [--spec ] [--limit ] [--all] [--prerelease] ``` List a skill's published versions, newest first, marking the current one. Use it to pick a version before pinning instead of guessing. `--spec` narrows the list to versions satisfying a range or exact version. Prereleases are hidden unless `--prerelease` is passed or `--spec` names one. ```sh localskills versions my-skill localskills versions my-skill --spec '^1.2.0' ``` ## pull ```sh localskills pull [slug] [--version | --latest] [--frozen] ``` Update installed skills. Omit the slug to update everything. Each skill re-resolves against its stored pin, so a skill pinned to `^1.2.0` stays on that line. 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. If any pulled skill requires MCP servers you don't have, pull prints the matching `localskills mcp install` commands (it never prompts). | Flag | Description | | --- | --- | | `--version ` | Re-pin this skill to a spec (exact, range, or integer) and update to it | | `--latest` | Clear the pin so the skill follows the newest version again | | `--frozen` | Fail with `E_VERSION_CHANGED` instead of moving anything, a CI drift check | `--version` and `--latest` require a skill argument and are mutually exclusive. A pin is only written after the update lands, so a re-pin that fails to resolve leaves the previous pin intact. ```sh localskills pull my-skill --version 1.2.3 # pin localskills pull my-skill --latest # unpin localskills pull --frozen # CI: fail on drift ``` ## publish ```sh 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) | | `-p, --parents` | Create missing folders in the --folder path | | `--personal` | Publish privately into your managed personal workspace; incompatible with --folder and --parents | | `--create` | Fail if a skill with this name already exists (never update, never auto-rename) | | `--update` | Fail unless this appends a version to an existing skill (never create) | By default `publish` is an upsert: a name that already belongs to a skill you can write to appends a new version, and anything else creates a new skill (auto-renaming the slug on collision). Automation that must never guess can pin the outcome: `--create` fails with `E_ALREADY_EXISTS` when the name is taken, and `--update` fails with `E_NOT_FOUND` when there is nothing to update, or `E_AMBIGUOUS` (listing the candidates in `-o json` details) when several same-name skills exist across folders. Pass `-f` to pick a folder, or target a skill id directly with `push`. ## push ```sh localskills push --skill [options] ``` Push a new version of an *existing* skill: the non-interactive counterpart to `publish`, built 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 highest published version; build metadata is not supported) | | `--patch` | Bump the patch version | | `--minor` | Bump the minor version | | `--major` | Bump the major version | | `--prerelease [id]` | Bump to a prerelease (default id `beta`); combines with `--patch`/`--minor`/`--major`. A prerelease is published but does not become the version installs resolve to. | | `-m, --message ` | Version message | ```sh localskills push skill.md --skill my-skill --patch -m "Fix typos" ``` ## share ```sh localskills share [path] [options] ``` | Flag | Description | | --- | --- | | `-n, --name ` | Skill display name | | `--type ` | skill or rule (default: skill) | Share a skill anonymously without 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 [Anonymous Sharing](/sharing) for details. ## folders ```sh 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 ```sh 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: ```sh localskills move my-skill marketing/seo localskills move my-skill / localskills move my-skill new/place --parents ``` ## profile ```sh 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. ```sh 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. ## mcp ```sh localskills mcp |uninstall |pull [ref]> ``` Install your team's managed MCP servers into your AI tools. `mcp list` shows what your teams offer, your MCP Central connectors, and which servers you have installed; `mcp install` writes config into each detected tool (Claude Code, Cursor, Windsurf, VS Code, Cline, Codex, opencode); `mcp pull` refreshes installs after an admin updates a server. Refs are `team/slug`, a bare slug when unambiguous, or the server id. Remote (http) servers install in one of two modes, chosen at install time: - **`--via local`** (today's flow): the server's own config is written into each tool; auth stays on your machine. - **`--via central`**: one shared `central` entry pointing at the MCP Central proxy is written instead. The CLI matches (or creates a personal) hosted connector for the server; you complete the upstream connection once in the dashboard, and every tool reaches it through the proxy. The shared entry is reference-counted: uninstalling one connector keeps it until the last one goes, and `mcp pull` never rewrites it. Without the flag, an interactive prompt asks (stdio servers always install locally). `localskills mcp install central` wires just the endpoint entry. ```sh localskills mcp install acme/github -t claude,cursor # prompts for mode localskills mcp install acme/github --via central # hosted proxy localskills mcp install acme/github --var GITHUB_TOKEN=$TOKEN --via local # CI localskills mcp pull ``` Member-supplied values (API keys) are prompted locally and written only into the tool's config file, never stored on localskills.sh; hosted connectors instead keep credentials encrypted server-side. See [MCP Servers](/mcp-servers) for the full model. ## Environment variables | Flag | 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_MCP_CENTRAL_URL` | MCP Central endpoint override for `mcp install --via central` (required on non-default API hosts) | | `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/, ...) ``` # Folders & Access Control > Organize skills into a folder tree and restrict folders to specific teams and members with viewer, editor, and manager grants. Source: https://docs.localskills.sh/folders/ · Markdown: https://docs.localskills.sh/folders/index.md 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: ```sh 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. > **Note** > > Deleting a folder requires it to be empty. Renaming or moving a folder also > renames its path in synced GitHub repos. ## Managed personal workspaces Organizations can enable [Personal Workspaces](/personal-workspaces) to provision one restricted workspace per member. Their namespace root and member folders use the same ACL model described below, but LocalSkills owns their lifecycle. Generic folder rename, move, deletion, and ACL operations reject managed folders; use the organization's Personal Workspaces page instead. ## 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, which 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. > **Note** > > 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). # Getting Started > Install the localskills CLI, authenticate, and install your first agent skill for Cursor, Claude Code, Windsurf, and more. Source: https://docs.localskills.sh/getting-started/ · Markdown: https://docs.localskills.sh/getting-started/index.md 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. ## 1. Install the CLI Install the `localskills` CLI globally via npm: ```sh npm i -g @localskills/cli ``` Or use `pnpm`, `yarn`, or `bun`: ```sh pnpm add -g @localskills/cli yarn global add @localskills/cli bun add -g @localskills/cli ``` Verify the installation: ```sh 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: ```sh localskills login ``` Your token is stored in `~/.localskills/config.json`. For headless environments (CI servers, containers, SSH sessions), create an API token in the dashboard and pass it directly: ```sh localskills login --token lsk_your_token_here ``` Verify your identity at any time: ```sh 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: ```sh localskills install my-skill ``` Skip the prompts by passing flags directly: ```sh 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: ```sh 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. > **Note** > > 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 [MCP server](/mcp); 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 [Organizations & Teams](/teams). **Publishing from CI?** Exchange your pipeline's OIDC token for platform access with no stored secrets; see [CI/CD & OIDC](/cicd). # GitHub Sync > Mirror your organization's skills to a GitHub repository: folder-structured, versioned, and continuously synced. Source: https://docs.localskills.sh/github-sync/ · Markdown: https://docs.localskills.sh/github-sync/index.md GitHub Sync mirrors your organization's entire skill library, folder structure included, to a GitHub repository. 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 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: ```text skills/ README.md # auto-generated skill index engineering/ reviews/ code-review-checklist/ .localskills.json # id, version, semver, type, visibility, tags, authors 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, tags, and author attribution: `author` is the skill's creator and `versionAuthor` is whoever published the mirrored version (each a `{ name, username }` object, or `null` for anonymous authors). The README index also lists each skill's author. These fields are informational: repo-side edits to them never sync back, and versions imported from repo pushes are attributed to the member who connected the repo, with the GitHub commit author preserved in the version message. Renaming or moving a folder on the platform renames the corresponding paths in the repo on the next sync. ## Security considerations > **Note** > > **All** skills are mirrored, including private and unlisted ones and skills > in [restricted folders](/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. # MCP Servers > Manage your team's MCP servers centrally on localskills.sh and install them into Claude Code, Cursor, Windsurf, VS Code, and other tools with one CLI command. Source: https://docs.localskills.sh/mcp-servers/ · Markdown: https://docs.localskills.sh/mcp-servers/index.md Team admins register **MCP server configurations** centrally, and members install them into their AI tools with one command. No more copy-pasting JSON snippets around Slack: the registry is the source of truth, and installs stay in sync. ## Registering a server Open `Dashboard → Connectors` and register a server, or import one from the curated catalog (the former MCP Servers page; old links redirect). The page shows one table for everything: locally installed registry servers and **MCP Central connectors** (remote servers proxied through one hosted endpoint instead of per-machine installs) coalesce into a single row per server, and each catalog entry installs either **for you** (a personal hosted connector) or **for the organization** (registry entry plus hosted connector in one click, for admins). Organizations can disable MCP Central entirely in `Dashboard → Team → Settings`; the local registry and CLI install flow are unaffected by that toggle. Three transports are supported, one local and two remote: | Flag | Description | | --- | --- | | `stdio` | A local process (e.g. an npx package): command, arguments, env vars | | `http / sse` | A hosted remote server: URL plus headers | Env vars and headers are declared as specs. Each spec is either a **fixed value** (plain, non-secret config the admin sets once) or **member-supplied** (each member provides their own value at install time). > **Note** > > Never put secrets in fixed values; they are stored in plain text on the > platform. Mark secrets as member-supplied instead; those values are prompted > locally and never reach localskills.sh. ## Installing List what your teams offer, then install: ```sh localskills mcp list localskills mcp install acme/github ``` By default the CLI installs into every detected tool that supports the server's transport. Narrow it with flags: | Flag | Description | | --- | --- | | `-t, --target ` | Comma-separated targets, e.g. claude,cursor | | `-g, --global` | Install into user-level configs | | `-p, --project [dir]` | Install into project configs (default: cwd) | | `--var NAME=value` | Provide a member-supplied value non-interactively (CI) | | `--force` | Overwrite an entry that localskills did not create | Supported tools and the config file each install writes: | Flag | Description | | --- | --- | | `claude` | `.mcp.json` (project) / `~/.claude.json` (global) | | `cursor` | `.cursor/mcp.json` (project) / `~/.cursor/mcp.json` (global) | | `windsurf` | `~/.codeium/windsurf/mcp_config.json` (global only) | | `copilot` | `.vscode/mcp.json` (project only, native inputs for secrets) | | `cline` | `~/.cline/data/settings/cline_mcp_settings.json` (global only) | | `codex` | `~/.codex/config.toml` (global only) | | `opencode` | `opencode.json` (project) / `~/.config/opencode/opencode.json` | > **Note** > > aider is not a target; it has no MCP support. Installing an org config means > your machine runs a command your team admins defined; it is the same trust > model as installing team skills. ## Linking servers to skills A **private** skill that depends on an MCP server (say, a Linear triage skill that needs the Linear server) can declare it as a **required MCP server**. Open the skill's `Edit` page and tick the servers it needs. Requirements are shown on the skill page, and `localskills install` offers to install any that are missing right after the skill itself: ```text localskills install acme/linear-triage ◇ Installed to 1 target(s). ◆ Install required MCP server Linear (acme/linear)? │ ● Yes / ○ No ``` Requirements exist only on private team skills, so installers are always members. The prompt appears in interactive terminals; CI and other non-interactive runs get a printed note with the exact `localskills mcp install` commands instead. Offers are location- and platform-aware: a server in your user-level config counts everywhere for that tool, a project install only counts for that project, and each targeted tool needs its own entry. Installing the skill in a new project, or adding a new tool (say Cursor after Claude Code), offers its servers there again, writing only the missing entries. Declining never affects the skill install, and `localskills pull` lists requirements added after you installed. > **Note** > > Requirements are limited to **private skills** for now: the MCP registry is > member-only, so a public installer couldn't fetch the servers anyway. Making > a linked skill public or unlisted is rejected until its requirements are > removed. Skill pages show only a required server's name, slug, and transport, > never its command, URL, env, or headers. Uninstalling a skill never removes > MCP servers; other skills or tools may share them. Requirements are set per skill (not per version) via the edit page or `PUT /api/skills/{id}` with `requiredMcpServerIds`, and only servers registered to the skill's own team can be required. Changing them needs `skill:update`. ## Secrets The platform stores only the *names* of member-supplied vars. At install the CLI prompts for values (hidden input for secrets) and writes them directly into your tool's own config file, exactly where that tool expects them. The VS Code target goes one step further: it emits native `inputs` declarations so VS Code prompts you itself and keeps values in its secret storage. ## Updating Every edit to a server bumps its revision. `localskills mcp list` flags installs that are behind, and: ```sh localskills mcp pull ``` rewrites drifted entries while preserving the values you supplied at install. If an update added a new required var, the CLI prompts for just that one. ## Permissions Browsing and installing requires the `mcpServer:read` permission, which the built-in member role has. Registering, editing, and deleting servers requires `mcpServer:create/update/delete`, held by owners and admins and grantable to custom roles. All changes appear in the team audit log. # MCP Server > Connect AI agents to localskills.sh via the Model Context Protocol. Setup for Claude Code, claude.ai, Cursor, Codex CLI, Windsurf, VS Code, and other MCP clients. Source: https://docs.localskills.sh/mcp/ · Markdown: https://docs.localskills.sh/mcp/index.md 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: ```text https://localskills.sh/mcp/mcp ``` It uses the **Streamable HTTP** transport (stateless mode). Each request is independent; no session management is 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: | Flag | 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: ```text https://localskills.sh/.well-known/oauth-authorization-server https://localskills.sh/.well-known/oauth-protected-resource/mcp/mcp ``` > **Note** > > 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): ```sh 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 } } } ``` > **Note** > > Rolling this out to a whole team? Register the server once as an > [organization MCP server](/mcp-servers) and members run > `localskills mcp pull`. The CLI writes the correct config format for every > tool above. ## 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 to 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 skills and rules visible to you. Returns matching skills with their slug, publicId, description, and current version; pass one to `get_skill_content` to load it. Use `scope: "personal"` to search only managed personal workspaces across your teams. The public catalog is not searchable over MCP. | Flag | 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 | | scope | all (default readable union), team (requires team and excludes managed personal workspaces), mine (skills you created), or personal (your managed personal workspace subtrees) | | 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](#bundled-files--signed-downloads)). Multi-file packages also include a signed `packageDownloadUrl` for the whole zip. | Flag | 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 | > **Note** > > 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. | Flag | 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 for chunked reads (default 0) | | 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. | Flag | 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. | Flag | 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. | Flag | 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. Returns the full result evidence: `publicId` (the immutable id to record for later `publish_version` calls), final `slug`, `tenantId`, `folderId`, `visibility`, `version`, `semver`, and the package `contentHash`. | Flag | 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"` | | destination | "personal" creates the skill in your managed personal workspace folder. Mutually exclusive with folder, requires private visibility, and needs [Personal Workspaces](/personal-workspaces) enabled for the organization (fails with a coded error otherwise) | | onConflict | "suffix" (default) auto-renames the slug when the name is taken; "error" fails with E_CONFLICT naming the existing skill instead of creating a renamed sibling | ### 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](/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. | Flag | 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`. Returns the full result evidence: `publicId`, `slug`, `tenantId`, `folderId`, `visibility`, `version`, `semver`, and the package `contentHash`. | Flag | Description | | --- | --- | | slug | The skill slug, or its publicId when the slug is ambiguous (required). Slugs are sibling-unique, not global, so prefer the publicId returned by create_skill or search to target the skill exactly | | 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 one 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. It's 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`: | Flag | Description | | --- | --- | | 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. 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, with no manual intervention. > **Note** > > 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 To load skills from localskills.sh automatically when a session starts, add 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` so Claude Code loads 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, 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**: add MCP skill loading to your Cursor project rules: ```text 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 gives teams shared coding standards and guidelines that every agent session picks up automatically. Update a skill on localskills.sh and every agent gets the latest version on its next session (no reinstall needed). > **Note** > > 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: ```sh curl https://localskills.sh/mcp/health ``` Returns `{"status":"ok"}` when healthy. # Personal Workspaces > Managed private workspaces provisioned automatically for every organization member. Source: https://docs.localskills.sh/personal-workspaces/ · Markdown: https://docs.localskills.sh/personal-workspaces/index.md Personal Workspaces give every organization member a private, organization-managed place to create individualized skills. LocalSkills provisions the folder and access grant automatically; integrations target the semantic personal destination instead of managing folder paths or per-user onboarding automation. ## Enable and provision > **Note** > > Personal Workspaces are rolling out to selected organizations. If the page > below is not in your Organization tabs, the feature is not yet available for > your organization. Contact support to be included. An owner or administrator opens **Organization > Personal Workspaces**, chooses a namespace such as `individuals`, and enables the feature. Existing members are backfilled. Every later membership automatically creates a durable provisioning request. Each member's workspace folder is named after the local part of their email address, lowercased with dots and other separators turned into hyphens: `jane.doe@example.com` gets a workspace named `jane-doe` at the path `individuals/jane-doe`. The folder name and its path segment are always the same. Members whose email yields no usable name fall back to their username, then their display name, and finally to `member` if neither yields a usable name either. When two members' emails map to the same path, the second member gets a deterministic suffixed path. If a folder already exists at the chosen namespace path, enabling adopts it as the managed namespace instead of failing; a folder that is already a managed Personal Workspaces object still conflicts. During the backfill, a folder that already sits at a member's expected path (for example a hand-built `individuals/jane-doe`) becomes that member's workspace, keeping its skills in place, unless it is already another member's workspace, in which case the member gets a deterministic suffixed path instead. Adopted folders become restricted managed objects. Each member workspace is a restricted child of the managed namespace with a direct `editor` grant for that member. Grants never exceed organization-role permissions, so a read-only member remains read-only. > **Note** > > The namespace and member workspaces are managed objects. Rename or move the > namespace, retry provisioning, and repair access from the Personal Workspaces > page—not from generic folder controls. Renaming or moving the namespace onto a path where an unmanaged folder already exists adopts that folder as the new namespace root: the current root's contents move into it (folder, grant, and skill identifiers preserved), the folder becomes restricted, and the old root is removed. Adoption is refused when the folder is already a managed Personal Workspaces object, or when the current namespace and the folder both contain an entry at the same path; move or rename one of the entries first. ## Publish to your workspace The dashboard exposes **My personal workspace**. The CLI and MCP use the same semantic destination: ```sh localskills publish ./my-skill --personal ``` ```js create_skill({ name: "My Skill", content: "...", tenantId: "org-id", destination: "personal" }) ``` Personal publications are always private. Responses include the physical `folderId`, but callers should keep using the personal destination for future creates. ## Find personal skills MCP callers use `search_skills` with `scope: "personal"`. This keeps discovery in the existing search tool while making the security boundary explicit. The CLI exposes the equivalent filter through `localskills list --personal`. ## Membership and lifecycle behavior - Joining an enabled organization schedules provisioning atomically. - Removing a member makes their binding inert; content is retained. - Rejoining reuses the existing workspace when it remains valid. - Changing the namespace path preserves child folder and skill IDs. - Administrators can inspect pending, failed, active, and former-member workspaces. See [Folders & Access Control](/folders) for the underlying restriction model and [MCP Server](/mcp) for complete tool schemas. # Roles & Permissions > Built-in organization roles, custom roles with granular permissions, and how effective permissions combine across roles and teams on localskills.sh. Source: https://docs.localskills.sh/roles/ · Markdown: https://docs.localskills.sh/roles/index.md 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. > **Note** > > 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](/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](/folders). Role changes are captured in the audit log as `role.created`, `role.updated`, `role.deleted`, and `organization.member_role_updated`. # SCIM 2.0 > Automate user and group provisioning with SCIM 2.0 directory sync from your identity provider. Source: https://docs.localskills.sh/scim/ · Markdown: https://docs.localskills.sh/scim/index.md 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** ```text 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). ## Provider guides ## 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 [Roles & Permissions](/roles) and [Organizations & Teams](/teams). # Security > Security practices for localskills.sh — rate limiting, security headers, authentication security, and content limits. Source: https://docs.localskills.sh/security/ · Markdown: https://docs.localskills.sh/security/index.md ## 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 | # Anonymous Sharing > Share agent skills anonymously without creating an account. Learn how the localskills share command works. Source: https://docs.localskills.sh/sharing/ · Markdown: https://docs.localskills.sh/sharing/index.md 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, never 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): ```sh localskills share ./my-skill.md localskills share ./my-skill-folder ``` Or run without arguments to scan for skill files and select interactively: ```sh localskills share ``` Options: ```sh # 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. > **Note** > > 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`. # Skills > Agent skill types, text and package formats, visibility settings, versioning, supported platforms, and install methods on localskills.sh. Source: https://docs.localskills.sh/skills/ · Markdown: https://docs.localskills.sh/skills/index.md 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 optional 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**. > **Note** > > Package skills install as folder-based Agent Skills on Claude Code, Cursor, > Codex, Windsurf, GitHub Copilot, Cline, OpenCode, and Flue, each in that > tool's native skills directory. Aider supports single-file text skills only, so > package installs skip it with a notice. ## 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 [Folders & Access Control](/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. ## Author attribution Every version records who published it, and the skill itself records its creator. The skill page shows the creator alongside a contributor strip (everyone who has published a version in the skill's lineage), and the dashboard's Versions tab lists the author of each version. Authorship is provenance, not ownership: publishing rights come from roles and folder access controls, so in an open folder any member with the skill create permission can publish a new version onto an existing skill. Versions imported through [GitHub Sync](/github-sync) are attributed to the member who connected the repo, with the GitHub commit author preserved in the version message. ## 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 | | Flue | `.agents/skills/{slug}/SKILL.md` | Project only | Copy | | Aider (text skills only) | `.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`). # SSO / SAML 2.0 > Configure SAML 2.0 Single Sign-On for localskills.sh with Okta, Azure AD, Google Workspace, and other identity providers. Source: https://docs.localskills.sh/sso/ · Markdown: https://docs.localskills.sh/sso/index.md 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) ```text https://localskills.sh/api/auth/sso/saml2/sp/metadata?providerId={providerId} ``` **ACS URL** ```text 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**, plus 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. ## Provider guides ## 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 [Organizations & Teams](/teams) and [Roles & Permissions](/roles). Syncs are audited as `sso.team_sync` and mapping changes as `sso.group_mappings_updated`. > **Note** > > All SSO configuration changes are tracked in the organization audit log. # Organizations & Teams > Create an organization, invite members, and group them into role-carrying teams, with SSO group mapping and role-based access control for agent skills. Source: https://docs.localskills.sh/teams/ · Markdown: https://docs.localskills.sh/teams/index.md 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 (lowercase alphanumeric and hyphens) is auto-generated from the name. 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](/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](/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`. > **Note** > > Map IdP groups to *teams*, then attach roles to those teams. This is how > directory group membership becomes 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.