Skill Installer
JetBrainsOfficialCreated by OpenAIDistributed by JetBrains
- Skill
- Workflow
- Agent tooling
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The skill-installer provides a structured way to manage the capabilities of a Codex agent. It allows users to browse available skills from a curated repository or install specific skills directly from GitHub paths. The tool handles the retrieval process, including support for private repositories, and ensures that new skills are correctly placed within the local environment. It serves as the primary interface for extending agent functionality by automating the download and directory setup required for new skills to be recognized by the system.
What it does
- Lists available skills from curated or experimental repositories.
- Installs new skills from specified GitHub repository paths.
- Supports installation from private GitHub repositories using existing credentials.
- Provides status information on whether a skill is already installed.
- Automates the directory placement of skills into the local Codex environment.
When to use it
- When a user wants to see what additional capabilities are available for their agent.
- When a user needs to add a specific skill from a GitHub repository.
- When a user wants to explore experimental features or community-contributed skills.
- When setting up a new agent environment that requires specific custom skills.
Advantages
- Simplifies the process of extending agent functionality.
- Provides a unified interface for both curated and custom skill sources.
- Handles complex git operations like sparse checkouts automatically.
- Integrates seamlessly with existing git authentication for private access.
Usage examples
- “What skills can I install?”
- The agent lists the available skills from the curated repository, indicating which ones are already present in the local environment.
- “Install the web-search skill from the curated list.”
- The agent downloads the specified skill and prompts the user to restart Codex to finalize the installation.
- “Install a custom skill from my private repo at github.com/myorg/my-skills/tree/main/my-tool.”
- The agent uses the installer to fetch the skill from the private repository and places it in the local skills directory.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/jetbrains/skill-installer/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip skill-installer.zip -d .agents/skills
Documentation by OpenAI
Skill Installer
Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations.
Use the helper scripts based on the task:
List skills when the user asks what is available, or if the user uses this skill without specifying what to do. Default listing is
.curated, but you can pass--path skills/.experimentalwhen they ask about experimental skills.Install from the curated list when the user provides a skill name.
Install from another repo when the user provides a GitHub repo/path (including private repos).
Install skills with the helper scripts.
Communication
When listing skills, output approximately as follows, depending on the context of the user's request. If they ask about experimental skills, list from .experimental instead of .curated and label the source accordingly:
"""
Skills from {repo}:
skill-1
skill-2 (already installed)
... Which ones would you like installed? """
After installing a skill, tell the user: "Restart Codex to pick up new skills."
Scripts
All of these scripts use network, so when running in the sandbox, request escalation when running them.
scripts/list-skills.py(prints skills list with installed annotations)scripts/list-skills.py --format jsonExample (experimental list):
scripts/list-skills.py --path skills/.experimentalscripts/install-skill-from-github.py --repo <owner>/<repo> --path <path/to/skill> [<path/to/skill> ...]scripts/install-skill-from-github.py --url https://github.com/<owner>/<repo>/tree/<ref>/<path>Example (experimental skill):
scripts/install-skill-from-github.py --repo openai/skills --path skills/.experimental/<skill-name>
Behavior and Options
Defaults to direct download for public GitHub repos.
If download fails with auth/permission errors, falls back to git sparse checkout.
Aborts if the destination skill directory already exists.
Installs into
$CODEX_HOME/skills/<skill-name>(defaults to~/.codex/skills).Multiple
--pathvalues install multiple skills in one run, each named from the path basename unless--nameis supplied.Options:
--ref <ref>(defaultmain),--dest <path>,--method auto|download|git.
Notes
Curated listing is fetched from
https://github.com/openai/skills/tree/main/skills/.curatedvia the GitHub API. If it is unavailable, explain the error and exit.Private GitHub repos can be accessed via existing git credentials or optional
GITHUB_TOKEN/GH_TOKENfor download.Git fallback tries HTTPS first, then SSH.
The skills at https://github.com/openai/skills/tree/main/skills/.system are preinstalled, so no need to help users install those. If they ask, just explain this. If they insist, you can download and overwrite.
Installed annotations come from
$CODEX_HOME/skills.
Files
- ATTRIBUTION.mdAdded by AutoGineer920 B
- LICENSE.txt11.1 KiB
- SKILL.md3.3 KiB
- agents/openai.yaml221 B
- assets/skill-installer-small.svg923 B
- assets/skill-installer.png1.0 KiB
- scripts/github_utils.pyScript659 B
- scripts/install-skill-from-github.pyScript9.9 KiB
- scripts/list-skills.pyScript2.9 KiB
scripts/install-skill-from-github.py
#!/usr/bin/env python3
"""Install a skill from a GitHub repo path into $CODEX_HOME/skills."""
from __future__ import annotations
import argparse
from dataclasses import dataclass
import os
import shutil
import subprocess
import sys
import tempfile
import urllib.error
import urllib.parse
import zipfile
from github_utils import github_request
DEFAULT_REF = "main"
@dataclass
class Args:
url: str | None = None
repo: str | None = None
path: list[str] | None = None
ref: str = DEFAULT_REF
dest: str | None = None
name: str | None = None
method: str = "auto"
@dataclass
class Source:
owner: str
repo: str
ref: str
paths: list[str]
repo_url: str | None = None
class InstallError(Exception):
pass
def _codex_home() -> str:
return os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex"))
def _tmp_root() -> str:
base = os.path.join(tempfile.gettempdir(), "codex")
os.makedirs(base, exist_ok=True)
return base
def _request(url: str) -> bytes:
return github_request(url, "codex-skill-install")
def _parse_github_url(url: str, default_ref: str) -> tuple[str, str, str, str | None]:
parsed = urllib.parse.urlparse(url)
if parsed.netloc != "github.com":
raise InstallError("Only GitHub URLs are supported for download mode.")
parts = [p for p in parsed.path.split("/") if p]
if len(parts) < 2:
raise InstallError("Invalid GitHub URL.")
owner, repo = parts[0], parts[1]
ref = default_ref
subpath = ""
if len(parts) > 2:
if parts[2] in ("tree", "blob"):
if len(parts) < 4:
raise InstallError("GitHub URL missing ref or path.")
ref = parts[3]
subpath = "/".join(parts[4:])
else:
subpath = "/".join(parts[2:])
return owner, repo, ref, subpath or None
def _download_repo_zip(owner: str, repo: str, ref: str, dest_dir: str) -> str:
zip_url = f"https://codeload.github.com/{owner}/{repo}/zip/{ref}"
zip_path = os.path.join(dest_dir, "repo.zip")
try:
payload = _request(zip_url)
except urllib.error.HTTPError as exc:
raise InstallError(f"Download failed: HTTP {exc.code}") from exc
with open(zip_path, "wb") as file_handle:
file_handle.write(payload)
with zipfile.ZipFile(zip_path, "r") as zip_file:
_safe_extract_zip(zip_file, dest_dir)
top_levels = {name.split("/")[0] for name in zip_file.namelist() if name}
if not top_levels:
raise InstallError("Downloaded archive was empty.")
if len(top_levels) != 1:
raise InstallError("Unexpected archive layout.")
return os.path.join(dest_dir, next(iter(top_levels)))
def _run_git(args: list[str]) -> None:
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode != 0:
raise InstallError(result.stderr.strip() or "Git command failed.")
def _safe_extract_zip(zip_file: zipfile.ZipFile, dest_dir: str) -> None:
dest_root = os.path.realpath(dest_dir)
for info in zip_file.infolist():
extracted_path = os.path.realpath(os.path.join(dest_dir, info.filename))
if extracted_path == dest_root or extracted_path.startswith(dest_root + os.sep):
continue
raise InstallError("Archive contains files outside the destination.")
zip_file.extractall(dest_dir)
def _validate_relative_path(path: str) -> None:
if os.path.isabs(path) or os.path.normpath(path).startswith(".."):
raise InstallError("Skill path must be a relative path inside the repo.")
def _validate_skill_name(name: str) -> None:
altsep = os.path.altsep
if not name or os.path.sep in name or (altsep and altsep in name):
raise InstallError("Skill name must be a single path segment.")
if name in (".", ".."):
raise InstallError("Invalid skill name.")
def _git_sparse_checkout(repo_url: str, ref: str, paths: list[str], dest_dir: str) -> str:
repo_dir = os.path.join(dest_dir, "repo")
clone_cmd = [
"git",
"clone",
"--filter=blob:none",
"--depth",
"1",
"--sparse",
"--single-branch",
"--branch",
ref,
repo_url,
repo_dir,
]
try:
_run_git(clone_cmd)
except InstallError:
_run_git(
[
"git",
"clone",
"--filter=blob:none",
"--depth",
"1",
"--sparse",
"--single-branch",
repo_url,
repo_dir,
]
)
_run_git(["git", "-C", repo_dir, "sparse-checkout", "set", *paths])
_run_git(["git", "-C", repo_dir, "checkout", ref])
return repo_dir
def _validate_skill(path: str) -> None:
if not os.path.isdir(path):
raise InstallError(f"Skill path not found: {path}")
skill_md = os.path.join(path, "SKILL.md")
if not os.path.isfile(skill_md):
raise InstallError("SKILL.md not found in selected skill directory.")
def _copy_skill(src: str, dest_dir: str) -> None:
os.makedirs(os.path.dirname(dest_dir), exist_ok=True)
if os.path.exists(dest_dir):
raise InstallError(f"Destination already exists: {dest_dir}")
shutil.copytree(src, dest_dir)
def _build_repo_url(owner: str, repo: str) -> str:
return f"https://github.com/{owner}/{repo}.git"
def _build_repo_ssh(owner: str, repo: str) -> str:
return f"[email protected]:{owner}/{repo}.git"
def _prepare_repo(source: Source, method: str, tmp_dir: str) -> str:
if method in ("download", "auto"):
try:
return _download_repo_zip(source.owner, source.repo, source.ref, tmp_dir)
except InstallError as exc:
if method == "download":
raise
err_msg = str(exc)
if "HTTP 401" in err_msg or "HTTP 403" in err_msg or "HTTP 404" in err_msg:
pass
else:
raise
if method in ("git", "auto"):
repo_url = source.repo_url or _build_repo_url(source.owner, source.repo)
try:
return _git_sparse_checkout(repo_url, source.ref, source.paths, tmp_dir)
except InstallError:
repo_url = _build_repo_ssh(source.owner, source.repo)
return _git_sparse_checkout(repo_url, source.ref, source.paths, tmp_dir)
raise InstallError("Unsupported method.")
def _resolve_source(args: Args) -> Source:
if args.url:
owner, repo, ref, url_path = _parse_github_url(args.url, args.ref)
if args.path is not None:
paths = list(args.path)
elif url_path:
paths = [url_path]
else:
paths = []
if not paths:
raise InstallError("Missing --path for GitHub URL.")
return Source(owner=owner, repo=repo, ref=ref, paths=paths)
if not args.repo:
raise InstallError("Provide --repo or --url.")
if "://" in args.repo:
return _resolve_source(
Args(url=args.repo, repo=None, path=args.path, ref=args.ref)
)
repo_parts = [p for p in args.repo.split("/") if p]
if len(repo_parts) != 2:
raise InstallError("--repo must be in owner/repo format.")
if not args.path:
raise InstallError("Missing --path for --repo.")
paths = list(args.path)
return Source(
owner=repo_parts[0],
repo=repo_parts[1],
ref=args.ref,
paths=paths,
)
def _default_dest() -> str:
return os.path.join(_codex_home(), "skills")
def _parse_args(argv: list[str]) -> Args:
parser = argparse.ArgumentParser(description="Install a skill from GitHub.")
parser.add_argument("--repo", help="owner/repo")
parser.add_argument("--url", help="https://github.com/owner/repo[/tree/ref/path]")
parser.add_argument(
"--path",
nargs="+",
help="Path(s) to skill(s) inside repo",
)
parser.add_argument("--ref", default=DEFAULT_REF)
parser.add_argument("--dest", help="Destination skills directory")
parser.add_argument(
"--name", help="Destination skill name (defaults to basename of path)"
)
parser.add_argument(
"--method",
choices=["auto", "download", "git"],
default="auto",
)
return parser.parse_args(argv, namespace=Args())
def main(argv: list[str]) -> int:
args = _parse_args(argv)
try:
source = _resolve_source(args)
source.ref = source.ref or args.ref
if not source.paths:
raise InstallError("No skill paths provided.")
for path in source.paths:
_validate_relative_path(path)
dest_root = args.dest or _default_dest()
tmp_dir = tempfile.mkdtemp(prefix="skill-install-", dir=_tmp_root())
try:
repo_root = _prepare_repo(source, args.method, tmp_dir)
installed = []
for path in source.paths:
skill_name = args.name if len(source.paths) == 1 else None
skill_name = skill_name or os.path.basename(path.rstrip("/"))
_validate_skill_name(skill_name)
if not skill_name:
raise InstallError("Unable to derive skill name.")
dest_dir = os.path.join(dest_root, skill_name)
if os.path.exists(dest_dir):
raise InstallError(f"Destination already exists: {dest_dir}")
skill_src = os.path.join(repo_root, path)
_validate_skill(skill_src)
_copy_skill(skill_src, dest_dir)
installed.append((skill_name, dest_dir))
finally:
if os.path.isdir(tmp_dir):
shutil.rmtree(tmp_dir, ignore_errors=True)
for skill_name, dest_dir in installed:
print(f"Installed {skill_name} to {dest_dir}")
return 0
except InstallError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Python
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- OpenAI
- Publisher
- JetBrains (Official)
- Distributed by
- JetBrains
- Source
- JetBrains/skills · skill-installer · commit 3f53782
- Licence
- Apache License 2.0 (redistribution allowed)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by OpenAI or JetBrains.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download skill-installer.zip