# 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.
]