main 628dce65304d cached
64 files
1.1 MB
283.8k tokens
575 symbols
1 requests
Download .txt
Showing preview only (1,176K chars total). Download the full file or copy to clipboard to get everything.
Repository: skillcreatorai/Ai-Agent-Skills
Branch: main
Commit: 628dce65304d
Files: 64
Total size: 1.1 MB

Directory structure:
gitextract_zrmsvrzw/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── config.yml
│   │   └── skill-request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── validate.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CURATION.md
├── FOR_YOUR_AGENT.md
├── LICENSE
├── README.md
├── WORK_AREAS.md
├── atlas.html
├── cli.js
├── curator.html
├── lib/
│   ├── catalog-data.cjs
│   ├── catalog-mutations.cjs
│   ├── catalog-paths.cjs
│   ├── dependency-graph.cjs
│   ├── frontmatter.cjs
│   ├── install-metadata.cjs
│   ├── install-state.cjs
│   ├── library-context.cjs
│   ├── paths.cjs
│   ├── render-docs.cjs
│   ├── source.cjs
│   └── workspace-import.cjs
├── package.json
├── scripts/
│   ├── render-docs.js
│   ├── test-live.js
│   ├── validate.js
│   └── vendor.js
├── skills/
│   ├── ask-questions-if-underspecified/
│   │   └── SKILL.md
│   ├── audit-library-health/
│   │   └── SKILL.md
│   ├── backend-development/
│   │   └── SKILL.md
│   ├── best-practices/
│   │   ├── SKILL.md
│   │   ├── agents/
│   │   │   ├── best-practices-referencer.md
│   │   │   ├── codebase-context-builder.md
│   │   │   └── task-intent-analyzer.md
│   │   └── references/
│   │       ├── anti-patterns.md
│   │       ├── before-after-examples.md
│   │       ├── best-practices-guide.md
│   │       ├── common-workflows.md
│   │       └── prompt-patterns.md
│   ├── browse-and-evaluate/
│   │   └── SKILL.md
│   ├── build-workspace-docs/
│   │   └── SKILL.md
│   ├── changelog-generator/
│   │   └── SKILL.md
│   ├── code-documentation/
│   │   └── SKILL.md
│   ├── content-research-writer/
│   │   └── SKILL.md
│   ├── curate-a-team-library/
│   │   └── SKILL.md
│   ├── database-design/
│   │   └── SKILL.md
│   ├── install-from-remote-library/
│   │   └── SKILL.md
│   ├── llm-application-dev/
│   │   └── SKILL.md
│   ├── migrate-skills-between-libraries/
│   │   └── SKILL.md
│   ├── review-a-skill/
│   │   └── SKILL.md
│   ├── share-a-library/
│   │   └── SKILL.md
│   └── update-installed-skills/
│       └── SKILL.md
├── skills.json
├── test.js
└── tui/
    ├── catalog.cjs
    └── index.mjs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: MoizIbnYousaf
custom: ["https://moizibnyousaf.com"]


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: Bug Report
description: Report an issue with a skill or install flow
title: "[Bug] "
labels: ["bug"]
body:
  - type: input
    id: skill
    attributes:
      label: Skill Name
      description: Which skill has the issue?
      placeholder: e.g., frontend-design
    validations:
      required: true

  - type: textarea
    id: description
    attributes:
      label: What happened?
      description: Describe the bug
    validations:
      required: true

  - type: textarea
    id: expected
    attributes:
      label: Expected behavior
      description: What should have happened?

  - type: input
    id: agent
    attributes:
      label: Agent
      description: Which AI agent are you using?
      placeholder: e.g., Claude Code, Cursor, Amp

  - type: input
    id: command
    attributes:
      label: Command
      description: What command did you run?
      placeholder: e.g., npx ai-agent-skills install frontend-design --agent cursor

  - type: textarea
    id: logs
    attributes:
      label: Relevant logs
      description: Any error messages or output
      render: shell


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Browse Collections
    url: https://github.com/MoizIbnYousaf/Ai-Agent-Skills#collections
    about: Start with the main shelves I use to organize the repo
  - name: Read the Curation Guide
    url: https://github.com/MoizIbnYousaf/Ai-Agent-Skills/blob/main/CURATION.md
    about: Read this before opening an issue or PR


================================================
FILE: .github/ISSUE_TEMPLATE/skill-request.yml
================================================
name: Skill Request
description: Suggest a skill to add
title: "[Curation] "
labels: ["skill-request"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for the suggestion. This repo is curated, so the best requests are the ones with a clear case for why a skill should be here.

  - type: input
    id: skill-name
    attributes:
      label: Skill Name
      description: What should this skill be called?
      placeholder: e.g., slack-automation
    validations:
      required: true

  - type: textarea
    id: description
    attributes:
      label: What should this skill do?
      description: Describe the job it should do and where it would help
      placeholder: |
        I want a skill that can...

        Use cases:
        - ...
        - ...
    validations:
      required: true

  - type: dropdown
    id: category
    attributes:
      label: Category
      options:
        - Development
        - Document
        - Creative
        - Business
        - Productivity
    validations:
      required: true

  - type: dropdown
    id: collection
    attributes:
      label: Closest collection
      description: If this deserves a top-level shelf, which one is the closest fit?
      options:
        - My Picks
        - Build Apps
        - Build Systems
        - Test and Debug
        - Docs and Research
        - No top-level collection
        - Not sure

  - type: textarea
    id: curation-rationale
    attributes:
      label: Why should I add this?
      description: Tell me why this is worth keeping around.
      placeholder: |
        Why would you actually reach for this?

        What does it do better than a one-off prompt?

        If it already exists somewhere else, why bring it into this repo?
    validations:
      required: true

  - type: input
    id: source
    attributes:
      label: Source repo or link
      description: If this already exists somewhere, link it here.
      placeholder: e.g., https://github.com/org/repo/tree/main/skills/my-skill

  - type: textarea
    id: examples
    attributes:
      label: Example prompts
      description: How would you use this skill?
      placeholder: |
        "Create a Slack message to #engineering about the deployment"
        "Summarize this channel's messages from today"


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Summary

Brief description of the change.

## Type

- [ ] New skill
- [ ] Skill update/fix
- [ ] Documentation
- [ ] Other

## Why Add This

Explain why this is worth keeping in this repo.

## Collection Fit

- [ ] `my-picks`
- [ ] `build-apps`
- [ ] `build-systems`
- [ ] `test-and-debug`
- [ ] `docs-and-research`
- [ ] No top-level collection

## Checklist

- [ ] SKILL.md has valid YAML frontmatter with `name` and `description`
- [ ] Skill name is lowercase with hyphens only
- [ ] Added entry to `skills.json`
- [ ] Added the skill to a collection if it clearly belongs on one
- [ ] Tested the skill works as expected
- [ ] Ran `node test.js`

## Attribution

Source repo, author, or origin notes if relevant.

## Skill Details (if adding new skill)

**Name:**
**Category:**
**Description:**


================================================
FILE: .github/workflows/validate.yml
================================================
name: Validate Skills

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Validate catalog
        run: node scripts/validate.js

      - name: Run tests
        run: node test.js

      - name: Validate CLI loads
        run: node cli.js list > /dev/null && echo "✓ CLI loads successfully"

      - name: Verify publish surface
        run: npm pack --dry-run 2>&1 && echo "✓ Package contents verified"


================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/

# Build outputs
dist/
build/

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
.cursor/
*.swp
*.swo

# Logs
*.log
npm-debug.log*

# Test coverage
coverage/

# Temporary files
tmp/
temp/

# Internal working papers
docs/*
!docs/workflows/
docs/workflows/*
!docs/workflows/*.md
!docs/releases/
docs/releases/*
!docs/releases/*.md

# Local install artifacts
.skills/

# Local workspace and desktop state
.agents/
.codex/
test-lib/
ws-test/
tmp-test-review-lib/
ai-agent-skills-*.tgz
ai-agent-skills-workflow.html


================================================
FILE: .npmignore
================================================
# Development files
.git
.github
.cursor
.gitignore
.npmignore

# Test files
test.js
scripts/

# Local install artifacts
.skills/
tmp/

# Documentation (README is auto-included)
CONTRIBUTING.md
CHANGELOG.md
CURATION.md
WORK_AREAS.md
docs/

# Dev tools
curator.html
atlas.html

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode
.idea
*.swp
*.swo


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

## [4.2.0] - 2026-03-31

### Added
- Remote shared-library installs that detect managed workspaces, expose parseable `--list` and `--dry-run` output, and resolve house copies plus upstream picks from one install flow.
- Authored workflow skills for `audit-library-health`, `browse-and-evaluate`, `build-workspace-docs`, `migrate-skills-between-libraries`, `review-a-skill`, and `update-installed-skills`.
- Wider machine-readable command support with JSON schemas, stdin mutation input, field masks, pagination, and dry-run coverage across more workflows.

### Changed
- Refined the shared-library story around team curation, shelf-first browsing, and a stronger "for your agent" handoff protocol.
- Tightened remote install errors and dry-run plans so non-interactive use stays predictable and actionable.
- Updated the README and curator-facing docs so the public surface matches the 4.2.0 library-manager state.

### Fixed
- Corrected shared-library dependency resolution so house copies install from the library while upstream entries keep their own recorded source.
- Hardened preview and install surfaces against suspicious content and invalid path-style inputs.
- Preserved workspace installs after workspace moves and improved unavailable-source messaging when a shared library can no longer be found.

## [4.0.0] - 2026-03-27

### Added
- Managed workspace mode with `.ai-agent-skills/config.json` and `init-library` scaffolding.
- The `add` command for bringing bundled picks, upstream repo skills, and house copies into a workspace library.
- The `build-docs` command for regenerating workspace `README.md` and `WORK_AREAS.md`.
- Dependency-aware catalog installs with `requires` and `--no-deps`.
- A shared install-state index used by the CLI and TUI.
- An `Installed` top-level TUI view and an empty-workspace onboarding state.
- Authored workflow guides for starting a library, adding upstream skills, making house copies, organizing shelves, and refreshing installs.

### Changed
- Promoted `sync` to the primary refresh command and kept `update` as a compatibility alias.
- Routed CLI and TUI library reads through active library resolution, so commands now follow bundled mode or workspace mode based on the current directory.
- Reframed the README and package surface around `ai-agent-skills` as a library manager, not only the bundled curated library.
- Split the README quick start into bundled-library and managed-workspace flows.

### Fixed
- Restored installed workspace catalog skills after workspace moves when commands run inside the relocated workspace.
- Tightened the npm publish surface so only workflow docs ship from `docs/`.
- Enforced duplicate-dependency validation for `requires`.
- Preserved explicit GitHub refs when cataloged upstream skills are stored as install metadata.

## [3.4.3] - 2026-03-21

### Changed
- Changed the default TUI opening view back to the boxed shelf and source grid so `ai-agent-skills` lands directly on the card-based library browser instead of the poster-text lead view.
- Restored the focused home inspector under the grid so the opening screen keeps the richer shelf/source preview while staying in the boxed layout.

### Removed
- Removed the temporary poster-home renderer and its compact-visibility helper now that the boxed library view is the default again.

## [3.4.2] - 2026-03-21

### Changed
- Tightened the TUI home into a stronger shelf-first poster layout with one dominant lead shelf or source and quieter neighboring picks below it.
- Replaced the last internal `atlas` wording in the TUI with consistent `library`, `shelves`, and `sources` language.

### Fixed
- Fixed TUI boot so the library opens from the top of the terminal pane instead of landing partway down the first screen.
- Removed the startup/loading card from the initial TUI frame so the first visible render is the actual library, not a boot placeholder.

## [3.4.1] - 2026-03-21

### Changed
- Simplified the TUI to the two real browse modes, `Shelves` and `Sources`, so the library opens directly into the taxonomy instead of a separate home summary.
- Renamed the overlapping frontend lanes to `Frontend (Anthropic)` and `Frontend (OpenAI)` so the publisher distinction is obvious while browsing.
- Tightened shelf and source cards with more editorial copy and less filler metadata so the first scan feels more like a library and less like a dashboard.
- Restored the README note that this repo launched before `skills.sh` and began as a universal installer before becoming a personal curated library.

### Fixed
- Corrected the source card footer pluralization in the TUI (`shelves`, not `shelfs`).

## [3.4.0] - 2026-03-21

### Added
- Added a first-class `curate` command for editing shelf placement, editorial notes, tags, labels, trust, verification state, and removals without hand-editing `skills.json`.
- Added a shared catalog mutation engine so CLI cataloging, curator edits, vendoring, and generated docs all run through the same validation and write path.
- Added generated-doc rendering with drift checks for `README.md` and `WORK_AREAS.md`, plus an internal `render:docs` maintenance script.
- Added a TUI curator loop with inline overlays for reviewing the library, editing the focused skill, and adding new upstream picks from GitHub repos.

### Changed
- Locked normal intake to upstream-only behavior: `catalog` now accepts GitHub repos, requires full shelf placement, and refuses partial or blank editorial entries.
- Tightened `vendor` into the explicit house-copy path, with the same editorial metadata requirements as the upstream catalog flow.
- Renamed the two overlapping frontend lanes so they read by publisher: `Frontend (Anthropic)` and `Frontend (OpenAI)`.
- Simplified the TUI to the two real browse modes, `Shelves` and `Sources`, with the old home summary removed from the top-level navigation.
- Rewrote shelf and source lane cards with more editorial copy and less generic metadata filler so the first scan reads like a curated library, not a utility dashboard.
- Synced the README and work-area map from the catalog so shelf counts and tables stop drifting.
- Restored the README note that this repo launched before `skills.sh` and started life as a universal installer before becoming a personal skills library.

### Removed
- Removed `figma-implement-design` from the curated library and the frontend shelf.

## [3.3.0] - 2026-03-21

### Changed
- Reworked the TUI home into a poster-style shelf browser with one dominant lead block, quieter neighboring shelves, and calmer chrome across the header, tabs, and footer.
- Reordered skill detail screens so the editorial note leads before install actions, with provenance and neighboring shelf picks kept visible without crowding the first frame.
- Polished `list` and `info` so the CLI reads like the same curated library as the TUI instead of a diagnostic catalog dump.

### Fixed
- Restored bundled `SKILL.md` loading in the TUI catalog so vendored skills can actually show real preview content again.
- Tightened the publish surface with an explicit npm `files` allowlist so temporary live-test reports and other local artifacts do not leak into the package tarball.

## [3.2.0] - 2026-03-21

### Added
- Added explicit `tier`, `distribution`, `notes`, and `labels` support to the catalog model.
- Added three new OpenAI skills: `figma-implement-design`, `security-best-practices`, and `notion-spec-to-implementation`.
- Added regression coverage for nested upstream installs, update-after-install, sparse upstream dry runs, and explicit tier metadata.
- Added a no-mock live verification suite that clones real upstream repos, captures raw source snapshots, exercises install/update/uninstall flows, and smoke-tests the TUI through a PTY.

### Changed
- Reframed the library around 10 shelves and rebuilt the collections around the current catalog.
- Normalized upstream install sources to exact repo subpaths so single-skill installs can use sparse checkout.
- Redesigned the CLI list output and TUI home around the bookshelf model instead of a flat catalog view.
- Rewrote the README, work-area map, and changelog to match the current two-tier architecture.
- Bumped the package and catalog version to `3.2.0`.

### Fixed
- Fixed nested upstream installs for cataloged skills such as `frontend-skill`, `shadcn`, and `emil-design-eng`.
- Fixed upstream installs so `update` works immediately after install with normalized `.skill-meta.json` metadata.
- Fixed TUI scope installs so upstream skills install correctly in both global and project scopes.
- Fixed project-scope lifecycle commands so `list --installed`, `update`, and `uninstall` now work against `.agents/skills/`, not only legacy agent targets.
- Fixed `preview` so upstream skills no longer print a false "not found" error before showing the fallback preview.
- Fixed root-skill renaming so local root skills keep their frontmatter name instead of inheriting a temp directory name.
- Fixed the TUI skill screen so upstream skills without bundled markdown no longer crash when opened from search.

## [3.1.0] - 2026-03-21

### Added
- Introduced the two-tier library model: house copies plus cataloged upstream skills.
- Added the `catalog` command for curating skills from GitHub repos without vendoring them.
- Added the React + Ink terminal browser and the curation atlas in `tui/`.
- Added validation for folder parity, schema integrity, and catalog totals.

### Changed
- Reduced the library from the older 48-skill set to a tighter curated shelf of 33 skills.
- Shifted the product from a generic installer toward an editorial library with provenance, trust, and `whyHere` notes.
- Moved the default install model to two scopes: global and project.

### Fixed
- Hardened install paths against traversal and unsafe name handling.
- Improved source parsing across GitHub shorthand, full URLs, local paths, and `@skill` filters.

## [1.9.2] - 2026-01-23

### Added
- `best-practices` skill to the registry.

## [1.9.1] - 2026-01-17

### Fixed
- Hardened git URL installs with validation, safer temp directories, and cleaner metadata handling.
- Added support for `ssh://` git URLs and corrected bin script paths.

## [1.9.0] - 2026-01-16

### Added
- Imported Vercel and Expo skills.
- Added framework tags for filtering.

### Fixed
- Added missing `SKILL.md` files for the imported Vercel and Expo entries.

## [1.8.0] - 2026-01-12

### Added
- Gemini CLI support with `--agent gemini` and install path `~/.gemini/skills/`.

### Changed
- Support expanded to 11 major agents.
- Updated README and package metadata for Gemini CLI support.

## [1.7.0] - 2026-01-04

### Fixed
- Improved metadata handling for sourced skills.
- Corrected the OpenCode path and related install messaging.

## [1.6.2] - 2026-01-01

### Changed
- Aligned help text and README copy with all-agent installs as the default behavior.

## [1.6.1] - 2026-01-01

### Added
- `ask-questions-if-underspecified` skill.

### Changed
- `install` now targets all supported agents by default.

## [1.6.0] - 2025-12-26

### Added
- Multi-agent operations with repeated or comma-separated `--agent` flags.

## [1.2.3] - 2025-12-26

### Fixed
- Corrected the OpenCode path from `skills` to `skill`.
- Removed the private `xreply` skill and cleaned related help text.

## [1.2.2] - 2025-12-25

### Fixed
- Added Windows path support.
- Hardened install and publish behavior before npm release.

## [1.2.1] - 2025-12-25

### Fixed
- Allowed installs where the repo root itself is the skill.

## [1.2.0] - 2025-12-20

### Added
- Interactive `browse` command.
- Install support from GitHub repos and local paths.

## [1.1.1] - 2025-12-20

### Added
- `doc-coauthoring` skill from Anthropic.

## [1.1.0] - 2025-12-20

### Added
- `--dry-run` mode to preview installs.
- Config file support through `~/.agent-skills.json`.
- Update notifications and `update --all`.
- Category filtering, tag search, and typo suggestions.
- `config` command and expanded validation tests.

### Changed
- Node.js 14+ became an explicit requirement.
- CLI output improved around skill size and help text.

### Fixed
- Better JSON and file-operation error handling.
- Partial installs are now cleaned up on failure.

### Security
- Blocked path traversal patterns in skill names.
- Enforced a 50 MB skill size limit during copy operations.

## [1.0.8] - 2025-12-20

### Added
- `uninstall` command.
- `update` command.
- `list --installed` flag.
- Letta agent support.
- Command aliases: `add`, `remove`, `rm`, `find`, `show`, `upgrade`.

### Fixed
- Description truncation only adds `...` when needed.

## [1.0.7] - 2025-12-19

### Added
- Credits and attribution section in the README.
- npm downloads badge.
- Full skill listing in the README.

### Fixed
- `--agent` flag parsing.
- Codex agent support.

## [1.0.6] - 2025-12-18

### Added
- 15 new skills from the ComposioHQ ecosystem:
  - `artifacts-builder`
  - `changelog-generator`
  - `competitive-ads-extractor`
  - `content-research-writer`
  - `developer-growth-analysis`
  - `domain-name-brainstormer`
  - `file-organizer`
  - `image-enhancer`
  - `invoice-organizer`
  - `lead-research-assistant`
  - `meeting-insights-analyzer`
  - `raffle-winner-picker`
  - `slack-gif-creator`
  - `theme-factory`
  - `video-downloader`
- Cross-link to the Awesome Agent Skills repository.

## [1.0.5] - 2025-12-18

### Fixed
- VS Code install message now correctly shows `.github/skills/`.

## [1.0.4] - 2025-12-18

### Fixed
- VS Code path corrected to `.github/skills/` from `.vscode/`.

## [1.0.3] - 2025-12-18

### Added
- `job-application` skill.

## [1.0.2] - 2025-12-18

### Added
- Multi-agent support with `--agent`.
- Support for Claude Code, Cursor, Amp, VS Code, Goose, OpenCode, and portable installs.

## [1.0.1] - 2025-12-18

### Added
- `qa-regression` skill.
- `jira-issues` skill.
- GitHub issue templates and PR templates.
- CI validation workflow.
- Funding configuration.

## [1.0.0] - 2025-12-17

### Added
- Initial release with 20 curated skills.
- NPX installer: `npx ai-agent-skills install <name>`.
- Skills from Anthropic's official examples.
- Core document skills: `pdf`, `xlsx`, `docx`, `pptx`.
- Development skills including `frontend-design`, `mcp-builder`, and `skill-creator`.
- Creative skills including `canvas-design` and `algorithmic-art`.


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to AI Agent Skills

This repo is curated.

Most of the library is sourced from other repos, so attribution and provenance matter as much as the skill itself.

Before you open a PR, read [CURATION.md](./CURATION.md).

## Good Additions

A skill is a good fit when it is:

- clear about what it does and when to use it
- reusable in real workflows
- strong enough to beat a generic prompt
- well-structured and easy for an agent to follow
- properly attributed

If a skill is fine but does not add much, I would rather leave it out.

## Requirements

1. The skill must follow the [Agent Skills specification](https://agentskills.io/specification).
2. `SKILL.md` must include valid YAML frontmatter with `name` and `description`.
3. The skill name must be lowercase with hyphens only, for example `my-skill`.
4. The skill should actually work and provide value.
5. Your PR should explain why this deserves a place in the library.

## Process

1. Fork the repo.
2. Add the skill folder at `skills/<skill-name>/`.
3. Add or update the `skills.json` entry, including the right `workArea` and `branch`.
4. Keep the source repo, `sourceUrl`, and attribution clean.
5. Set the right trust level and sync mode. Most additions should start as `listed` and either `mirror` or `snapshot`.
6. Make sure the work area and tags are clean. Put the skill in a collection only if it clearly belongs on one of the shorter CLI shelves.
7. Run `node test.js`.
8. Open a PR with a short explanation of why it belongs.

## Categories

Use one of these:

- `development`
- `document`
- `creative`
- `business`
- `productivity`

## Collections

These are the top-level shelves:

- `my-picks`
- `build-apps`
- `build-systems`
- `test-and-debug`
- `docs-and-research`

Not every skill needs one. If tags and search do the job, say that in the PR.

## Review Bar

I review submissions for:

- usefulness
- clarity
- overlap with existing skills
- source quality and provenance
- attribution and licensing
- overall fit with the repo

## Updating Existing Skills

If you are improving a skill that is already here:

1. Keep attribution intact unless ownership has clearly changed.
2. Explain what you changed and why it is better.
3. Say whether it belongs on a top-level shelf, should become featured, or should become verified.

## Questions

If you are not sure whether something belongs, open an issue first. That is usually faster.


================================================
FILE: CURATION.md
================================================
# Curation Guide

This is my keep pile.

I am not trying to mirror every agent skill on the internet. I want a strong set of skills I would actually keep on a machine, recommend, and keep improving.
Most of them come from other repos, so curation here is as much about provenance and trust as it is about the skill text.

## What I Care About

I want skills here to be:

- genuinely useful in real work
- clear enough that an agent can follow them well
- reusable across more than one project
- good enough to beat a generic prompt
- worth maintaining

If a skill does not clear that bar, I leave it out.

## What Usually Does Not Belong

- weak rewrites of skills that already exist here
- novelty skills that will feel dead in a month
- skills that are so narrow they are not worth maintaining
- skills with unclear attribution or licensing
- prompt dumps pretending to be skills

## How I Keep It Organized

I keep the folder structure simple and let the catalog do the sorting.

- `skills/` holds the actual skill folders
- `skills.json` is the catalog the CLI reads
- `workArea` and `branch` are the main browse fields in the catalog
- `work areas` are the main browse model
- `collections` are the shorter CLI shelves
- `category`, `tags`, `source`, `sourceUrl`, `origin`, `syncMode`, `featured`, `verified`, and `trust` help with sorting and trust

I do not want a deep folder tree. It makes install tooling worse and the repo harder to maintain.

## Work Areas And Collections

The main browse model is work area first, source repo second.

Collections are useful, but they are not meant to cover everything.

- `my-picks`: the fastest way to understand my taste
- `build-apps`: web and mobile product work with a high interface bar
- `build-systems`: backend, architecture, MCP, and deeper engineering work
- `test-and-debug`: review, QA, debugging, and cleanup work
- `docs-and-research`: docs, files, research, and execution support

Not every skill needs a collection. If something is useful but off to the side, search and tags can do the job.

## Featured And Verified

- `featured: true` means I would point people to that skill first
- `verified: true` means I have personally checked it and I am comfortable signaling more trust

Those markers should mean something. They should stay a little hard to earn.

## Trust Levels

- `listed` means the skill belongs in the library, but I am not signaling much beyond that yet
- `reviewed` means I have put a little more editorial weight behind it
- `verified` means I have personally checked it and I am comfortable standing behind it more directly

## Mirrors And Snapshots

- `mirror` means the local copy still tracks a clean upstream counterpart closely
- `snapshot` means I am intentionally shipping a stable vendored copy even if upstream has moved
- `adapted` means the library copy is based on outside work but changed enough that I do not want to pretend it is a straight mirror
- `authored` means I maintain the skill directly here

## Agent Support

I am keeping support focused on the major agents.

I do not want to spend time adding support for every new coding agent that launches, especially if I do not use it or do not think it will matter in six months.

If support is here, it should be worth the maintenance burden.

## Maintainer Workflow

When I add or update a skill, I try to answer these questions:

1. Is this actually good?
2. Does it belong here?
3. What is the right category?
4. Does it deserve a top-level shelf, or should it stay tag-driven?
5. Is it good enough to feature?
6. Have I checked enough to verify it?
7. Is the attribution clean?

## If This Turns Into A Website

The structure is already here.

- home page: library first, with work areas and source repos both visible
- browse page: collections, tags, source repos, and search
- skill page: source, tags, collections when relevant, install command
- trust layer: featured, verified, and catalog trust state

The repo should stay where the data lives. A site can present it better.


================================================
FILE: FOR_YOUR_AGENT.md
================================================
# For Your Agent

Use this when you want an agent to build and share a managed skills library for you, not just make a local folder of `SKILL.md` files.

For detailed workflow guidance, install the skill: `npx ai-agent-skills install curate-a-team-library`.

The companion workflow skills are:

- `npx ai-agent-skills install install-from-remote-library`
- `npx ai-agent-skills install curate-a-team-library`
- `npx ai-agent-skills install share-a-library`
- `npx ai-agent-skills install browse-and-evaluate`
- `npx ai-agent-skills install update-installed-skills`
- `npx ai-agent-skills install build-workspace-docs`
- `npx ai-agent-skills install review-a-skill`
- `npx ai-agent-skills install audit-library-health`
- `npx ai-agent-skills install migrate-skills-between-libraries`

## Paste this into your agent

```text
Set up a managed team skills library for me with `ai-agent-skills`.

Use this repo for reference if you need docs or examples:
https://github.com/MoizIbnYousaf/Ai-Agent-Skills
https://github.com/MoizIbnYousaf/Ai-Agent-Skills/blob/main/FOR_YOUR_AGENT.md

Use the CLI with `npx`. Do not ask me to open the repo or link you to anything else.
Do not hand-edit `skills.json`, `README.md`, or `WORK_AREAS.md` if the command already exists.

Follow this curator decision protocol:

1. Create a new workspace with `npx ai-agent-skills init-library <name>`, unless I already gave you a library name.
   - If I already have a flat repo of local skills, run `npx ai-agent-skills init-library . --import` from that repo root instead of creating a new directory.
   - Invalid private-only names such as colon or underscore variants should be skipped and reported, not allowed to kill the whole batch.
2. Move into that workspace and keep working there.
3. Ask me at most 3 short questions before acting:
   - what kinds of work the library needs to support
   - whether the first pass should stay small and opinionated or aim broader
   - whether this should end as a local draft only or a shareable GitHub repo
4. Use these 5 work areas as the shelf system:
   - `frontend` for web UI, browser work, design systems, visual polish
   - `backend` for APIs, databases, security, infrastructure, runtime systems
   - `mobile` for iOS, Android, React Native, Expo, device testing, app delivery
   - `workflow` for docs, testing, release work, files, research, planning
   - `agent-engineering` for prompts, evals, tools, orchestration, agent runtime design
5. Map the user's stack to shelves before adding anything.
   - Example: "I build mobile apps with React Native and a Node backend" maps to `mobile` + `backend`.
   - Add `workflow` only when testing, release, docs, or research are clearly part of the job.
   - Add `agent-engineering` only when the user is building AI features, agents, prompts, evals, or toolchains.
   - Make sure the first pass covers every primary shelf the user explicitly named. Do not let `mobile` crowd out `backend` if they asked for both.
6. Run a discovery loop before curating:
   - use `npx ai-agent-skills list --area <work-area>` to browse a shelf
   - use `npx ai-agent-skills search <query>` when the user names a stack, tool, or capability
   - use `npx ai-agent-skills collections` to inspect starter packs that may already exist
   - keep machine-readable reads tight with `--fields name,tier,workArea`
   - use `--limit 10` on larger result sets before asking for more
   - if the user named multiple primary shelves, browse each of them before deciding what to add
7. Keep the first pass small, around 3 to 8 skills.
8. Choose the right mutation path:
   - use `add` first for bundled picks and simple GitHub imports when the CLI can route it for you
   - use `catalog` when you want an upstream entry without copying files into `skills/`
   - use `vendor` only for true house copies you want to edit or own locally
9. Keep branch names consistent and useful.
   - Examples: `React Native / UI`, `React Native / QA`, `Node / APIs`, `Node / Data`, `Docs / Release`
   - Use branches to group related picks inside a shelf, not as free-form notes
10. Every mutation must include explicit curator metadata like `--area`, `--branch`, and `--why`.
11. Write `whyHere` notes as concrete curation reasoning, not placeholders.
   - good: "Covers React Native testing so the mobile shelf has a real device-validation option."
   - bad: "I want this on my shelf."
12. Use `--featured` sparingly.
   - keep it to about 2 to 3 featured skills per shelf
   - reserve it for skills you would tell a new teammate to install first
13. After the library has about 5 to 8 solid picks, create a `starter-pack` collection.
   - add new entries with `--collection starter-pack`
   - or use `npx ai-agent-skills curate <skill> --collection starter-pack` for existing entries
14. Sanity-check the library before finishing.
   - run `npx ai-agent-skills list --area <work-area>` for each primary shelf you touched
   - if you created `starter-pack`, run `npx ai-agent-skills collections` and confirm the install command looks right
15. Run `npx ai-agent-skills build-docs` before finishing.
16. If the user wants the library shared, turn it into a GitHub repo:
   - `git init`
   - `git add .`
   - `git commit -m "Initialize skills library"`
   - `gh repo create <owner>/<repo> --public --source=. --remote=origin --push`
17. End by telling me:
   - what you added
   - which shelves you used and why
   - which skills are featured
   - what the `starter-pack` includes, if you created one
   - the shareable install command
   - use `npx ai-agent-skills install <owner>/<repo> --collection starter-pack -p` when a starter pack exists
   - otherwise use `npx ai-agent-skills install <owner>/<repo> -p`
```

## Curator Decision Framework

Start with the workspace, not manual file edits. The job is to produce a library that another person or agent can actually browse, trust, and install.

### Shelf Mapping Rules

- `frontend`: web interfaces, design systems, browser automation, UI polish, app-shell UX.
- `backend`: APIs, auth, databases, data pipelines, infra, services, runtime behavior.
- `mobile`: React Native, Expo, SwiftUI, Kotlin, simulators, device QA, store delivery.
- `workflow`: testing, release work, docs, research, content ops, file transforms, planning.
- `agent-engineering`: prompts, evals, tool use, orchestration, memory, agent runtime patterns.

If a user gives a mixed stack, map it to more than one shelf. Do not force every skill into one branch. If the stack is "React Native + Node backend", the first shelves are `mobile` and `backend`, and you only pull in `workflow` or `agent-engineering` when the actual work justifies it.

The first pass should include at least one strong anchor skill for each primary shelf the user explicitly named.

### Discovery Loop

Before curating, inspect what already exists.

- Browse shelves with `npx ai-agent-skills list --area <work-area>`.
- Search by tools or capabilities with `npx ai-agent-skills search <query>`.
- Check `npx ai-agent-skills collections` when a ready-made pack may already cover part of the use case.
- In machine-readable flows, prefer `--fields name,tier,workArea` first so the response stays small.
- Add `--limit 10` when a shelf or search looks broad, then page further only if needed.
- If the user named multiple primary shelves, browse each one before you start curating.

Do not jump straight from `init-library` to a few guessed names unless the user already told you the exact skills they want.

### Add vs Catalog vs Vendor

- Use `add` as the default front door inside a workspace.
- Use `catalog` when the right move is "track this upstream skill in our library, but do not copy its files into `skills/`."
- Use `vendor` when the right move is "we want our own editable house copy in this library."

If the user wants a repo they can share across a team, prefer upstream catalog entries for third-party skills and reserve house copies for true internal ownership.

### Branch Naming

Keep branch labels consistent so the shelves stay readable.

- Good: `React Native / UI`, `React Native / QA`, `Node / APIs`, `Node / Data`, `Docs / Release`
- Bad: `stuff`, `misc`, `my notes`

### Writing Good `whyHere` Notes

`whyHere` is curator judgment. It should explain why this skill belongs in this library, on this shelf, for this team.

- Mention the actual gap it fills.
- Mention the stack or workflow it supports.
- Be honest about why it is here instead of a nearby alternative.
- Never use placeholders like "I want this" or "looks useful."

### Featured Skills

Featured picks are the shelf anchors.

- Keep featured picks to about 2 to 3 per shelf.
- Feature the skills a new teammate should notice first.
- Do not feature everything.

### Collections

Once the library has a meaningful first pass, create a `starter-pack` collection.

- Put the first recommended 3 to 5 skills in it.
- Make it cross-shelf when that helps onboarding.
- Use `curate --collection starter-pack` to retrofit membership onto skills that are already in the catalog.

### Final Sanity Check

Before you hand the library back:

- Run `npx ai-agent-skills list --area <work-area>` for each primary shelf you touched.
- Run `npx ai-agent-skills collections` if you created `starter-pack`.
- Make sure the resulting library still reflects the user’s actual stack and does not over-index on one shelf.

### Sharing Step

A library is not really shared until it is in Git and has an install command you can hand to someone else.

After `build-docs`, if the user wants sharing:

```bash
git init
git add .
git commit -m "Initialize skills library"
gh repo create <owner>/<repo> --public --source=. --remote=origin --push
```

Then give them the actual install command to share, for example:

```bash
npx ai-agent-skills install <owner>/<repo> --collection starter-pack -p
```

If you did not create a `starter-pack` yet, share the whole library instead:

```bash
npx ai-agent-skills install <owner>/<repo> -p
```

## Direct Shell Fallback

```bash
npx ai-agent-skills init-library my-library
cd my-library

npx ai-agent-skills list --area mobile
npx ai-agent-skills search react-native
npx ai-agent-skills search testing

npx ai-agent-skills add frontend-design --area frontend --branch Implementation --why "Anchors the frontend shelf with stronger UI craft and production-ready interface direction."
npx ai-agent-skills add anthropics/skills --skill webapp-testing --area workflow --branch Testing --why "Adds browser-level validation so the workflow shelf covers end-to-end checks." --collection starter-pack
npx ai-agent-skills catalog conorluddy/ios-simulator-skill --skill ios-simulator-skill --area mobile --branch "React Native / QA" --why "Gives the mobile shelf a concrete simulator workflow for app-level testing." --collection starter-pack --featured

npx ai-agent-skills build-docs

# Existing flat repo of skills
cd ~/projects/my-skills
npx ai-agent-skills init-library . --areas "mobile,workflow,agent-engineering" --import --auto-classify
npx ai-agent-skills list --area workflow
npx ai-agent-skills curate my-skill --area mobile --branch "Mobile / Imported" --why "Why it belongs."

git init
git add .
git commit -m "Initialize skills library"
gh repo create <owner>/my-library --public --source=. --remote=origin --push

# Share this with teammates:
npx ai-agent-skills install <owner>/my-library --collection starter-pack -p
```


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Moiz Ibn Yousaf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">AI Agent Skills</h1>

<p align="center">
  <strong>My curated library of agent skills, plus the package to build your own.</strong>
</p>

<p align="center">
  The skills I actually keep around, organized the way I work.
</p>

<!-- GENERATED:library-stats:start -->
<p align="center">
  <a href="https://github.com/MoizIbnYousaf/Ai-Agent-Skills"><img alt="GitHub stars" src="https://img.shields.io/github/stars/MoizIbnYousaf/Ai-Agent-Skills?style=for-the-badge&label=stars&labelColor=313244&color=89b4fa&logo=github&logoColor=cdd6f4" /></a>
  <a href="https://www.npmjs.com/package/ai-agent-skills"><img alt="npm version" src="https://img.shields.io/npm/v/ai-agent-skills?style=for-the-badge&label=version&labelColor=313244&color=b4befe&logo=npm&logoColor=cdd6f4" /></a>
  <a href="https://www.npmjs.com/package/ai-agent-skills"><img alt="npm total downloads" src="https://img.shields.io/npm/dt/ai-agent-skills?style=for-the-badge&label=downloads&labelColor=313244&color=f5e0dc&logo=npm&logoColor=cdd6f4" /></a>
  <a href="https://github.com/MoizIbnYousaf/Ai-Agent-Skills#shelves"><img alt="Library structure" src="https://img.shields.io/badge/library-110%20skills%20%C2%B7%206%20shelves-cba6f7?style=for-the-badge&labelColor=313244&logo=bookstack&logoColor=cdd6f4" /></a>
</p>

<p align="center"><sub>17 house copies · 93 cataloged upstream</sub></p>
<!-- GENERATED:library-stats:end -->

<p align="center"><em>Picked, shelved, and maintained by hand.</em></p>

<p align="center">
  <a href="./docs/workflows/start-a-library.md"><strong>Build your own library</strong></a>
  ·
  <a href="./FOR_YOUR_AGENT.md"><strong>For your agent</strong></a>
</p>

## Library

`ai-agent-skills` does two things.

It ships my curated library, and it gives you the CLI and TUI to build and manage your own.
It works with any Agent Skills-compatible agent.

The bundled library is organized the way I work:

- Start with a shelf like `frontend` or `workflow`
- Keep the set small enough to browse quickly
- Keep provenance visible
- Keep notes that explain why a skill is here

Use `skills.sh` for the broad ecosystem.
Use `ai-agent-skills` when you want a smaller library with shelves, provenance, and notes.

## What's New in 4.2.0

- Managed team libraries you can share over GitHub and install with `install <owner>/<repo>`
- Machine-readable CLI flows with `--format json`, `--fields`, pagination, and safer non-interactive output
- More authored workflow skills for curating, reviewing, syncing, and sharing libraries
- Dependency-aware installs, `sync` as the main refresh verb, and stronger installed-state visibility across the CLI and TUI
- A cleaner curator loop around shelves, provenance, trust, and shared starter packs

## What It Is Now

I launched this on December 17, 2025, before `skills.sh` existed and before the ecosystem had a clear default universal installer.

Originally this repo was that installer. It still does that.

What started as an installer is now a place to build and manage your own library of skills.

## How It Works

Each skill here is either a house copy or a cataloged upstream pick.

- `House copies`
  Local folders under `skills/<name>/`.
  These install fast, work offline, and ship with the npm package.

- `Cataloged upstream`
  Metadata in `skills.json` with no local folder.
  These stay upstream and install from the source repo when you ask for them.

Upstream work stays upstream. That keeps the library lean.

## For Your Agent

Tell your agent to build you a library. Paste this, or just point it at this repo — the protocol below has everything it needs.

Full protocol with curator decision framework: [FOR_YOUR_AGENT.md](./FOR_YOUR_AGENT.md)

### Paste this into your agent

```text
Set up a managed team skills library for me with `ai-agent-skills`.

Read the full agent protocol here before starting:
https://raw.githubusercontent.com/MoizIbnYousaf/Ai-Agent-Skills/main/FOR_YOUR_AGENT.md

Use the CLI with `npx`. Do not hand-edit `skills.json`, `README.md`, or `WORK_AREAS.md` if the command already exists.

1. Fetch and read FOR_YOUR_AGENT.md above — it has the full curator decision protocol.
2. Create a workspace with `npx ai-agent-skills init-library <name>`.
3. Ask me at most 3 short questions: what kinds of work, small or broad, local draft or shared repo.
4. Map my stack to shelves: frontend, backend, mobile, workflow, agent-engineering.
5. Run a discovery loop: `list --area <shelf>`, `search <query>`, `collections`.
6. Add 3-8 skills with explicit `--area`, `--branch`, and `--why` on every mutation.
7. Run `npx ai-agent-skills build-docs` before finishing.
8. If I want it shared: `git init && git add . && git commit -m "Initialize skills library" && gh repo create`.
9. Tell me what you added, which shelves, and the install command for teammates.
```

The companion workflow skills (installed automatically when you use the library):

```
npx ai-agent-skills install install-from-remote-library
npx ai-agent-skills install curate-a-team-library
npx ai-agent-skills install share-a-library
npx ai-agent-skills install browse-and-evaluate
npx ai-agent-skills install update-installed-skills
npx ai-agent-skills install build-workspace-docs
npx ai-agent-skills install review-a-skill
npx ai-agent-skills install audit-library-health
npx ai-agent-skills install migrate-skills-between-libraries
```

## Quick Start

### Use the bundled library

```bash
# Open the terminal browser
npx ai-agent-skills

# List the shelves
npx ai-agent-skills list

# Install a skill from the library
npx ai-agent-skills install frontend-design

# Install the Swift hub to the default global targets
npx ai-agent-skills swift

# Install an entire curated pack
npx ai-agent-skills install --collection swift-agent-skills -p

# Install the mktg marketing pack
npx ai-agent-skills mktg
npx ai-agent-skills marketing-cli

# Install to the project shelf
npx ai-agent-skills install pdf -p

# Install all skills from an upstream repo to the default global targets
npx ai-agent-skills anthropics/skills

# Browse a repo before adding or installing from it
npx ai-agent-skills install openai/skills --list
```

Default install targets:

- Global: `~/.claude/skills/`
- Project: `.agents/skills/`

Legacy agent-specific targets still work through `--agent <name>`.

### Start your own library

```bash
# Create a managed workspace
npx ai-agent-skills init-library my-library
cd my-library

# Add a bundled pick, install it, refresh it, and rebuild the docs
npx ai-agent-skills add frontend-design --area frontend --branch Implementation --why "I want this on my shelf."
npx ai-agent-skills install frontend-design -p
npx ai-agent-skills sync frontend-design -p
npx ai-agent-skills add anthropics/skills --skill webapp-testing --area workflow --branch Testing --why "I use this when I want browser-level checks in the workspace."
npx ai-agent-skills build-docs

# Or bootstrap an existing flat repo of skills in place
cd ~/projects/my-skills
npx ai-agent-skills init-library . --areas "mobile,workflow,agent-engineering" --import --auto-classify
npx ai-agent-skills browse

# Invalid private-only names are skipped and reported.
# Low-confidence imports fall back to workflow with a needs-curation label.
```

## Workspace Mode

Workspace mode is part of the normal flow now.

Start with a managed workspace, add a few skills, then keep your shelves current with `add`, `catalog`, `vendor`, `sync`, and `build-docs`.

```bash
npx ai-agent-skills init-library my-library
cd my-library

npx ai-agent-skills add frontend-design --area frontend --branch Implementation --why "I want this on my shelf."
npx ai-agent-skills install frontend-design -p
npx ai-agent-skills add anthropics/skills --skill webapp-testing --area workflow --branch Testing --why "I use this when I want browser-level checks in the workspace."
npx ai-agent-skills sync frontend-design -p
npx ai-agent-skills build-docs

# Bulk import an existing library after bootstrap
npx ai-agent-skills import --auto-classify

# Review the fallback bucket and fix shelf placement
npx ai-agent-skills list --area workflow
npx ai-agent-skills curate some-skill --area mobile --branch "Mobile / Testing" --why "Why it belongs."
```

Workflow guides:

- [Start a library](./docs/workflows/start-a-library.md)
- [Add an upstream skill](./docs/workflows/add-an-upstream-skill.md)
- [Make a house copy](./docs/workflows/make-a-house-copy.md)
- [Organize shelves](./docs/workflows/organize-shelves.md)
- [Refresh installed skills](./docs/workflows/refresh-installed-skills.md)

## Browse

Most browsing starts in one of two places:

| View | Why it exists | Start here |
| --- | --- | --- |
| Shelves | The main way to understand the library: start with the kind of work, then drill into the small set of picks on that shelf. | `npx ai-agent-skills list` |
| Sources | The provenance view: see which publishers feed which shelves and branches. | `npx ai-agent-skills info frontend-design` |

The other views are still useful, just more situational:

- `npx ai-agent-skills browse` for the TUI
- `npx ai-agent-skills list --collection my-picks` for a cross-shelf starter stack
- `npx ai-agent-skills install --collection swift-agent-skills -p` for an installable curated pack
- `npx ai-agent-skills curate review` for the curator cleanup queue

## Shelves

The shelves are the main structure.

<!-- GENERATED:shelf-table:start -->
| Shelf | Skills | What it covers |
| --- | --- | --- |
| Frontend | 10 | Interfaces, design systems, browser work, and product polish. |
| Backend | 5 | Systems, data, security, and runtime operations. |
| Mobile | 24 | Swift, SwiftUI, iOS, and Apple-platform development, with room for future React Native branches. |
| Workflow | 11 | Files, docs, planning, release work, and research-to-output flows. |
| Agent Engineering | 14 | MCP, skill-building, prompting discipline, and LLM application work. |
| Marketing | 46 | Brand, strategy, copy, distribution, creative, SEO, conversion, and growth work. |
<!-- GENERATED:shelf-table:end -->

The full map lives in [WORK_AREAS.md](./WORK_AREAS.md).

## Collections

Collections are smaller sets. Useful, but secondary to the shelves.

<!-- GENERATED:collection-table:start -->
| Collection | Why it exists | Start here |
| --- | --- | --- |
| `my-picks` | A short starter stack. These are the skills I reach for first. | `frontend-design`, `mcp-builder`, `pdf` |
| `build-apps` | Frontend, UI, and design work for shipping polished apps. | `frontend-design`, `frontend-skill`, `shadcn` |
| `swift-agent-skills` | The main Swift and Apple-platform set in this library. Install it all at once or pick from it. | `swiftui-pro`, `swiftui-ui-patterns`, `swiftui-design-principles` |
| `build-systems` | Backend, architecture, MCP, and security work. | `mcp-builder`, `backend-development`, `database-design` |
| `test-and-debug` | QA, debugging, CI cleanup, and observability. | `playwright`, `webapp-testing`, `gh-fix-ci` |
| `docs-and-research` | Docs, files, research, and writing work. | `pdf`, `doc-coauthoring`, `docx` |
| `mktg` | The full upstream mktg marketing playbook. Install the whole set at once or pick from it. | `cmo`, `brand-voice`, `positioning-angles` |
<!-- GENERATED:collection-table:end -->

## Curating the catalog

Use `catalog` when you want to add an upstream skill without vendoring it.

In a managed workspace, start with `add`.
Use `catalog` and `vendor` when you want more control.

```bash
npx ai-agent-skills catalog openai/skills --list
npx ai-agent-skills catalog openai/skills --skill linear --area workflow --branch Linear
npx ai-agent-skills catalog openai/skills --skill security-best-practices --area backend --branch Security
npx ai-agent-skills catalog conorluddy/ios-simulator-skill --skill ios-simulator-skill --area mobile --branch "Swift / Tools" --collection swift-agent-skills
npx ai-agent-skills catalog shadcn-ui/ui --skill shadcn --area frontend --branch Components
```

It does not create a local copy.
It adds metadata and placement in the active library:

- which shelf it belongs on
- what branch it lives under
- why it earned a place
- how it should install later

For existing picks, use `curate` for quick edits:

```bash
npx ai-agent-skills curate frontend-design --branch Implementation
npx ai-agent-skills curate ios-simulator-skill --collection swift-agent-skills
npx ai-agent-skills curate ios-simulator-skill --remove-from-collection swift-agent-skills
npx ai-agent-skills curate frontend-design --why "A stronger note that matches how I actually use it."
npx ai-agent-skills curate review
```

When I want a local copy, I use `vendor`:

```bash
npx ai-agent-skills vendor <repo-or-path> --skill <name> --area <shelf> --branch <branch> --why "Why this deserves a local copy."
npx ai-agent-skills vendor <repo-or-path> --skill <name> --area mobile --branch "Swift / Tools" --collection swift-agent-skills --why "Why this deserves a place in the Swift pack."
```

## Source Repos

Current upstream mix:

<!-- GENERATED:source-table:start -->
| Source repo | Skills |
| --- | --- |
| `MoizIbnYousaf/mktg` | 46 |
| `anthropics/skills` | 11 |
| `MoizIbnYousaf/Ai-Agent-Skills` | 11 |
| `openai/skills` | 9 |
| `Dimillian/Skills` | 4 |
| `wshobson/agents` | 4 |
| `rgmez/apple-accessibility-skills` | 3 |
| `ComposioHQ/awesome-claude-skills` | 2 |
| `andrewgleave/skills` | 1 |
| `arjitj2/swiftui-design-principles` | 1 |
| `AvdLee/Core-Data-Agent-Skill` | 1 |
| `AvdLee/Swift-Concurrency-Agent-Skill` | 1 |
| `AvdLee/Swift-Testing-Agent-Skill` | 1 |
| `bocato/swift-testing-agent-skill` | 1 |
| `conorluddy/ios-simulator-skill` | 1 |
| `dadederk/iOS-Accessibility-Agent-Skill` | 1 |
| `efremidze/swift-architecture-skill` | 1 |
| `emilkowalski/skill` | 1 |
| `Erikote04/Swift-API-Design-Guidelines-Agent-Skill` | 1 |
| `ivan-magda/swift-security-skill` | 1 |
| `PasqualeVittoriosi/swift-accessibility-skill` | 1 |
| `raphaelsalaja/userinterface-wiki` | 1 |
| `shadcn-ui/ui` | 1 |
| `twostraws/Swift-Concurrency-Agent-Skill` | 1 |
| `twostraws/Swift-Testing-Agent-Skill` | 1 |
| `twostraws/SwiftData-Agent-Skill` | 1 |
| `twostraws/SwiftUI-Agent-Skill` | 1 |
| `vanab/swiftdata-agent-skill` | 1 |
<!-- GENERATED:source-table:end -->

The two biggest upstream publishers in this library are Anthropic and OpenAI.
I browse, pick, and shelve. I do not mirror everything they publish.

## Commands

```bash
# Browse
npx ai-agent-skills
npx ai-agent-skills browse
npx ai-agent-skills list
npx ai-agent-skills list --work-area frontend
npx ai-agent-skills collections
npx ai-agent-skills search frontend
npx ai-agent-skills info frontend-design
npx ai-agent-skills preview pdf

# Install
npx ai-agent-skills install <skill-name>
npx ai-agent-skills swift
npx ai-agent-skills mktg
npx ai-agent-skills marketing-cli
npx ai-agent-skills install <skill-name> -p
npx ai-agent-skills install --collection swift-agent-skills -p
npx ai-agent-skills install --collection mktg -p
npx ai-agent-skills <owner/repo>
npx ai-agent-skills install <owner/repo>
npx ai-agent-skills install <owner/repo>@<skill-name>
npx ai-agent-skills install <owner/repo> --skill <name>
npx ai-agent-skills install <owner/repo> --list
npx ai-agent-skills install ./local-path
npx ai-agent-skills install <skill-name> --dry-run

# Maintain
npx ai-agent-skills sync [name]
npx ai-agent-skills uninstall <name>
npx ai-agent-skills check
npx ai-agent-skills doctor
npx ai-agent-skills validate [path]

# Curate
npx ai-agent-skills catalog <owner/repo> --list
npx ai-agent-skills catalog <owner/repo> --skill <name> --area <shelf> --branch <branch> --why "<editorial note>"
npx ai-agent-skills curate <skill-name> --branch "<branch>"
npx ai-agent-skills curate review
npx ai-agent-skills vendor <repo-or-path> --skill <name> --area <shelf> --branch <branch> --why "<editorial note>"
npx ai-agent-skills import [path] --auto-classify
```

## Testing

- `npm test`
  Fast regression coverage for CLI behavior, schema rules, routing, and local install flows.
- `npm run test:live`
  No-mock live verification. Clones the real upstream repos, captures raw `SKILL.md` frontmatter and file manifests, runs real install/sync/uninstall flows in isolated temp homes and projects, drives the TUI through a real PTY, and writes a report to `tmp/live-test-report.json`.
- `npm run test:live:quick`
  A smaller live matrix for faster iteration with the same no-mock pipeline.

## Legacy Agent Support

Still supported through `--agent <name>`:

- `claude`
- `cursor`
- `codex`
- `amp`
- `vscode`
- `copilot`
- `gemini`
- `goose`
- `opencode`
- `letta`
- `kilocode`
- `project`

## What I Care About

- Small shelves
- Clear provenance
- Notes that explain why something stays
- Upstream repos staying upstream
- A library that looks cared for

## Contributing

This is a curated library.

Read [CURATION.md](./CURATION.md) before opening a PR.

## Related

- [WORK_AREAS.md](./WORK_AREAS.md)
- [CURATION.md](./CURATION.md)
- [CONTRIBUTING.md](./CONTRIBUTING.md)
- [Agent Skills specification](https://agentskills.io)


================================================
FILE: WORK_AREAS.md
================================================
# Work Areas

Shelf map for the library.

House copies stay flat under `skills/<name>/`. The catalog holds the real structure.

## Frontend

10 skills. Interfaces, design systems, browser work, and product polish.

| Branch | Skills | Source |
| --- | --- | --- |
| Components | `shadcn` | shadcn-ui |
| Design Engineering | `figma`, `emil-design-eng` | openai, emilkowalski |
| Implementation | `frontend-design`, `frontend-skill` | anthropics, openai |
| Quality | `webapp-testing`, `playwright`, `userinterface-wiki` | anthropics, openai, raphaelsalaja |
| Visual Systems | `canvas-design`, `brand-guidelines` | anthropics |

## Backend

5 skills. Systems, data, security, and runtime operations.

| Branch | Skills | Source |
| --- | --- | --- |
| Architecture | `backend-development` | wshobson |
| Data | `database-design` | wshobson |
| Operations | `gh-fix-ci`, `sentry` | openai |
| Security | `security-best-practices` | openai |

## Mobile

24 skills. Swift, SwiftUI, iOS, and Apple-platform development, with room for future React Native branches.

| Branch | Skills | Source |
| --- | --- | --- |
| Swift / Accessibility | `ios-accessibility`, `swift-accessibility-skill`, `appkit-accessibility-auditor`, `swiftui-accessibility-auditor`, `uikit-accessibility-auditor` | dadederk, PasqualeVittoriosi, rgmez |
| Swift / Architecture | `swift-architecture-skill` | efremidze |
| Swift / Concurrency | `swift-concurrency-pro`, `swift-concurrency-expert`, `swift-concurrency` | twostraws, Dimillian, AvdLee |
| Swift / Core Data | `core-data-expert` | AvdLee |
| Swift / Language | `swift-api-design-guidelines-skill` | Erikote04 |
| Swift / Performance | `swiftui-performance-audit` | Dimillian |
| Swift / Security | `swift-security-expert` | ivan-magda |
| Swift / SwiftData | `swiftdata-pro`, `swiftdata-expert-skill` | twostraws, vanab |
| Swift / SwiftUI | `swiftui-pro`, `swiftui-ui-patterns`, `swiftui-design-principles`, `swiftui-view-refactor` | twostraws, Dimillian, arjitj2 |
| Swift / Testing | `swift-testing-pro`, `swift-testing`, `swift-testing-expert` | twostraws, bocato, AvdLee |
| Swift / Tools | `ios-simulator-skill` | conorluddy |
| Swift / User Interface | `writing-for-interfaces` | andrewgleave |

## Workflow

11 skills. Files, docs, planning, release work, and research-to-output flows.

| Branch | Skills | Source |
| --- | --- | --- |
| Files & Docs | `pdf`, `xlsx`, `docx`, `pptx`, `doc-coauthoring`, `code-documentation` | anthropics, wshobson |
| Planning | `linear`, `notion-spec-to-implementation` | openai |
| Release | `changelog-generator` | composio |
| Release & Sharing | `share-a-library` | MoizIbnYousaf |
| Research & Writing | `content-research-writer` | composio |

## Agent Engineering

14 skills. MCP, skill-building, prompting discipline, and LLM application work.

| Branch | Skills | Source |
| --- | --- | --- |
| Agent Behavior | `ask-questions-if-underspecified` | thsottiaux |
| Agent Workflows | `browse-and-evaluate`, `update-installed-skills`, `review-a-skill` | MoizIbnYousaf |
| LLM Apps | `llm-application-dev` | wshobson |
| MCP | `mcp-builder` | anthropics |
| Prompting | `best-practices` | MoizIbnYousaf |
| Provider Docs | `openai-docs` | openai |
| Shared Libraries | `install-from-remote-library`, `curate-a-team-library`, `build-workspace-docs`, `audit-library-health`, `migrate-skills-between-libraries` | MoizIbnYousaf |
| Skill Authoring | `skill-creator` | anthropics |

## Marketing

46 skills. Brand, strategy, copy, distribution, creative, SEO, conversion, and growth work.

| Branch | Skills | Source |
| --- | --- | --- |
| Conversion | `page-cro`, `conversion-flow-cro` | MoizIbnYousaf |
| Copy Content | `direct-response-copy`, `seo-content`, `lead-magnet` | MoizIbnYousaf |
| Creative | `creative`, `marketing-demo`, `paper-marketing`, `slideshow-script`, `video-content`, `tiktok-slideshow`, `frontend-slides`, `app-store-screenshots`, `visual-style`, `image-gen`, `brand-kit-playground` | MoizIbnYousaf |
| Distribution | `content-atomizer`, `email-sequences`, `newsletter`, `social-campaign`, `typefully`, `send-email`, `resend-inbound`, `agent-email-inbox` | MoizIbnYousaf |
| Foundation | `cmo`, `brand-voice`, `positioning-angles`, `audience-research`, `competitive-intel`, `landscape-scan`, `brainstorm`, `create-skill`, `deepen-plan`, `document-review`, `voice-extraction` | MoizIbnYousaf |
| Growth | `churn-prevention`, `referral-program`, `free-tool-strategy`, `startup-launcher` | MoizIbnYousaf |
| Knowledge | `marketing-psychology` | MoizIbnYousaf |
| SEO | `seo-audit`, `ai-seo`, `competitor-alternatives` | MoizIbnYousaf |
| Strategy | `keyword-research`, `launch-strategy`, `pricing-strategy` | MoizIbnYousaf |


================================================
FILE: atlas.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Atlas — Ai-Agent-Skills</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,500;0,9..40,700;1,9..40,300&family=JetBrains+Mono:wght@400;500&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
:root{--paper:#faf8f5;--ink:#1a1612;--stone:#6b6560;--warm:#c4b5a0;--sand:#e8e0d4;--amber:#b8860b;--rust:#9e4a2f;--sage:#5a7a60;--slate:#3a4a5a;--cream:#f0ebe3;--ghost:rgba(26,22,18,.04)}
html{font-family:'DM Sans',sans-serif;background:var(--paper);color:var(--ink);-webkit-font-smoothing:antialiased}
::selection{background:var(--amber);color:var(--paper)}

/* Layout */
.shell{display:grid;grid-template-columns:260px 1fr;grid-template-rows:auto 1fr;min-height:100vh}

/* Masthead */
.mast{grid-column:1/-1;padding:20px 32px;border-bottom:1px solid var(--sand);display:flex;align-items:baseline;gap:16px}
.mast h1{font-size:15px;font-weight:700;letter-spacing:-.3px;color:var(--ink)}
.mast h1 span{color:var(--amber);font-weight:300;margin-left:6px}
.mast .pills{display:flex;gap:6px;margin-left:auto}
.pill{font-size:11px;font-weight:500;color:var(--stone);padding:4px 10px;background:var(--cream);border-radius:4px;font-family:'JetBrains Mono',monospace;letter-spacing:-.2px}
.pill b{color:var(--ink);font-weight:500}

/* Rail */
.rail{border-right:1px solid var(--sand);padding:20px 0;overflow-y:auto;background:var(--paper)}
.rail-section{padding:0 16px;margin-bottom:20px}
.rail-label{font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--warm);font-weight:700;margin-bottom:8px;padding:0 4px}
.rail-btn{display:block;width:100%;text-align:left;border:none;background:none;font-family:inherit;font-size:12px;color:var(--stone);padding:6px 10px;border-radius:5px;cursor:pointer;transition:all .1s}
.rail-btn:hover{background:var(--cream);color:var(--ink)}
.rail-btn.on{background:var(--ink);color:var(--paper);font-weight:500}
.rail-btn .c{float:right;font-family:'JetBrains Mono',monospace;font-size:10px;opacity:.5}
.rail-btn.on .c{opacity:.7}
.rail-search{width:100%;border:1px solid var(--sand);background:var(--cream);padding:7px 10px;border-radius:5px;font-size:12px;font-family:inherit;color:var(--ink);outline:none;margin-bottom:12px}
.rail-search:focus{border-color:var(--amber)}
.rail-search::placeholder{color:var(--warm)}

/* Main */
.main{padding:24px 32px;overflow-y:auto}

/* Table */
.tbl{width:100%;border-collapse:collapse}
.tbl th{text-align:left;font-size:9px;text-transform:uppercase;letter-spacing:1.2px;color:var(--warm);font-weight:700;padding:0 12px 10px;border-bottom:1px solid var(--sand)}
.tbl td{padding:10px 12px;border-bottom:1px solid var(--ghost);font-size:13px;vertical-align:middle}
.tbl tr{transition:background .08s}
.tbl tr:hover{background:var(--cream)}
.tbl tr.selected{background:rgba(184,134,11,.06)}
.tbl .skill-name{font-weight:500;color:var(--ink);cursor:pointer}
.tbl .skill-name:hover{color:var(--amber)}
.tier-dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:6px}
.tier-dot.house{background:var(--amber)}
.tier-dot.upstream{background:var(--sage)}
.tier-label{font-size:11px;color:var(--stone)}
.src-tag{font-size:10px;font-family:'JetBrains Mono',monospace;color:var(--slate);background:var(--cream);padding:2px 6px;border-radius:3px}
.trust-tag{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:3px}
.trust-tag.verified{color:var(--sage);background:rgba(90,122,96,.1)}
.trust-tag.reviewed{color:var(--slate);background:rgba(58,74,90,.08)}
.trust-tag.listed{color:var(--warm);background:var(--ghost)}
.area-tag{font-size:10px;color:var(--stone)}

/* Actions column */
.acts{display:flex;gap:2px;opacity:0;transition:opacity .1s}
tr:hover .acts{opacity:1}
.act{border:none;background:none;cursor:pointer;padding:4px 6px;border-radius:4px;font-size:11px;font-family:inherit;color:var(--stone);transition:all .1s}
.act:hover{background:var(--cream);color:var(--ink)}
.act.danger:hover{color:var(--rust);background:rgba(158,74,47,.08)}

/* Detail drawer */
.drawer{position:fixed;right:0;top:0;bottom:0;width:420px;background:var(--paper);border-left:1px solid var(--sand);padding:28px;overflow-y:auto;transform:translateX(100%);transition:transform .2s ease-out;z-index:10;box-shadow:-8px 0 32px rgba(0,0,0,.04)}
.drawer.open{transform:translateX(0)}
.drawer-close{position:absolute;top:16px;right:16px;border:none;background:none;font-size:20px;color:var(--stone);cursor:pointer;padding:4px 8px;border-radius:4px}
.drawer-close:hover{background:var(--cream);color:var(--ink)}
.drawer h2{font-size:18px;font-weight:700;letter-spacing:-.4px;margin-bottom:4px}
.drawer .subtitle{font-size:12px;color:var(--stone);margin-bottom:20px}
.drawer-field{margin-bottom:14px}
.drawer-field .k{font-size:9px;text-transform:uppercase;letter-spacing:1.2px;color:var(--warm);font-weight:700;margin-bottom:3px}
.drawer-field .v{font-size:13px}
.drawer-sep{height:1px;background:var(--sand);margin:16px 0}
.drawer-cmd{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--sage);background:var(--cream);padding:10px 14px;border-radius:6px;position:relative;margin-top:8px;line-height:1.6}
.drawer-cmd .cp{position:absolute;top:6px;right:6px;border:1px solid var(--sand);background:var(--paper);color:var(--stone);padding:2px 8px;border-radius:3px;cursor:pointer;font-size:9px;font-family:inherit}
.drawer-cmd .cp:hover{border-color:var(--amber);color:var(--ink)}
.flow-steps{margin-top:8px}
.flow-step{display:flex;gap:10px;padding:5px 0;font-size:12px;color:var(--stone)}
.flow-step .n{width:18px;height:18px;border-radius:50%;background:var(--cream);color:var(--amber);display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;border:1px solid var(--sand)}

/* Drawer actions */
.drawer-actions{display:flex;gap:6px;margin-top:16px;flex-wrap:wrap}
.drawer-act{border:1px solid var(--sand);background:var(--paper);padding:6px 14px;border-radius:5px;font-size:11px;font-family:inherit;color:var(--stone);cursor:pointer;transition:all .1s}
.drawer-act:hover{border-color:var(--ink);color:var(--ink)}
.drawer-act.primary{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.drawer-act.primary:hover{background:var(--amber);border-color:var(--amber)}
.drawer-act.danger{color:var(--rust);border-color:rgba(158,74,47,.3)}
.drawer-act.danger:hover{background:rgba(158,74,47,.06);border-color:var(--rust)}

/* Toast */
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(80px);background:var(--ink);color:var(--paper);padding:10px 20px;border-radius:8px;font-size:12px;font-weight:500;opacity:0;transition:all .25s ease-out;z-index:20;pointer-events:none}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* Empty */
.empty{text-align:center;padding:60px 20px;color:var(--warm)}
.empty p{font-size:13px;margin-top:8px}
</style>
</head>
<body>
<div class="shell">
<div class="mast">
  <h1>Atlas<span>v3.1</span></h1>
  <div class="pills" id="pills"></div>
</div>
<div class="rail">
  <div class="rail-section">
    <input class="rail-search" id="search" placeholder="Search skills...">
  </div>
  <div class="rail-section">
    <div class="rail-label">Work Areas</div>
    <div id="areas"></div>
  </div>
  <div class="rail-section">
    <div class="rail-label">Tier</div>
    <div id="tiers"></div>
  </div>
  <div class="rail-section">
    <div class="rail-label">Source</div>
    <div id="sources"></div>
  </div>
</div>
<div class="main">
  <table class="tbl">
    <thead><tr><th style="width:28px"></th><th>Skill</th><th>Area / Branch</th><th>Source</th><th>Trust</th><th style="width:100px"></th></tr></thead>
    <tbody id="tbody"></tbody>
  </table>
</div>
</div>
<div class="drawer" id="drawer"></div>
<div class="toast" id="toast"></div>

<script>
const S=[
{name:"frontend-design",area:"frontend",branch:"React",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"figma-implement-design",area:"frontend",branch:"Figma",source:"openai/skills",trust:"reviewed",tier:"upstream"},
{name:"backend-development",area:"backend",branch:"Architecture",source:"wshobson/agents",trust:"listed",tier:"house"},
{name:"database-design",area:"backend",branch:"Database",source:"wshobson/agents",trust:"listed",tier:"house"},
{name:"pdf",area:"docs",branch:"PDF",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"xlsx",area:"docs",branch:"Spreadsheets",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"docx",area:"docs",branch:"Documents",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"pptx",area:"docs",branch:"Presentations",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"doc-coauthoring",area:"docs",branch:"Writing",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"code-documentation",area:"docs",branch:"Writing",source:"wshobson/agents",trust:"listed",tier:"house"},
{name:"webapp-testing",area:"testing",branch:"Web QA",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"playwright",area:"testing",branch:"Browser Automation",source:"openai/skills",trust:"reviewed",tier:"upstream"},
{name:"changelog-generator",area:"workflow",branch:"Release Notes",source:"ComposioHQ/awesome-claude-skills",trust:"listed",tier:"house"},
{name:"linear",area:"workflow",branch:"Linear",source:"openai/skills",trust:"listed",tier:"upstream"},
{name:"content-research-writer",area:"research",branch:"Writing",source:"ComposioHQ/awesome-claude-skills",trust:"listed",tier:"house"},
{name:"lead-research-assistant",area:"research",branch:"Lead Research",source:"ComposioHQ/awesome-claude-skills",trust:"listed",tier:"house"},
{name:"canvas-design",area:"design",branch:"Canvas",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"algorithmic-art",area:"design",branch:"Generative Art",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"figma",area:"design",branch:"Figma",source:"openai/skills",trust:"reviewed",tier:"upstream"},
{name:"video-downloader",area:"design",branch:"Video",source:"ComposioHQ/awesome-claude-skills",trust:"listed",tier:"house"},
{name:"brand-guidelines",area:"business",branch:"Brand",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"internal-comms",area:"business",branch:"Communication",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"job-application",area:"business",branch:"Career",source:"MoizIbnYousaf/Ai-Agent-Skills",trust:"verified",tier:"house"},
{name:"mcp-builder",area:"ai",branch:"MCP",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"skill-creator",area:"ai",branch:"Skills",source:"anthropics/skills",trust:"verified",tier:"upstream"},
{name:"llm-application-dev",area:"ai",branch:"LLMs",source:"wshobson/agents",trust:"reviewed",tier:"house"},
{name:"best-practices",area:"ai",branch:"Prompting",source:"MoizIbnYousaf/Ai-Agent-Skills",trust:"verified",tier:"house"},
{name:"ask-questions-if-underspecified",area:"ai",branch:"Agent Behavior",source:"MoizIbnYousaf/Ai-Agent-Skills",trust:"verified",tier:"house"},
{name:"openai-docs",area:"ai",branch:"OpenAI",source:"openai/skills",trust:"reviewed",tier:"upstream"},
{name:"gh-fix-ci",area:"devops",branch:"CI",source:"openai/skills",trust:"reviewed",tier:"upstream"},
{name:"sentry",area:"devops",branch:"Observability",source:"openai/skills",trust:"listed",tier:"upstream"},
];

const AREAS=['all','frontend','backend','docs','testing','workflow','research','design','business','mobile','ai','devops'];
let state={area:'all',tier:'all',source:'all',query:'',selected:null};

function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2000)}

function filtered(){
  return S.filter(s=>{
    if(state.area!=='all'&&s.area!==state.area)return false;
    if(state.tier!=='all'&&s.tier!==state.tier)return false;
    if(state.source!=='all'&&s.source!==state.source)return false;
    if(state.query){const q=state.query.toLowerCase();if(![s.name,s.area,s.branch,s.source].some(f=>f.toLowerCase().includes(q)))return false;}
    return true;
  });
}

function render(){
  const house=S.filter(s=>s.tier==='house').length;
  const up=S.filter(s=>s.tier==='upstream').length;
  document.getElementById('pills').innerHTML=`<span class="pill"><b>${S.length}</b> skills</span><span class="pill"><b>${house}</b> house</span><span class="pill"><b>${up}</b> upstream</span>`;

  // Areas
  const areaCounts={};S.forEach(s=>areaCounts[s.area]=(areaCounts[s.area]||0)+1);
  document.getElementById('areas').innerHTML=AREAS.map(a=>{
    const c=a==='all'?S.length:(areaCounts[a]||0);
    return `<button class="rail-btn ${state.area===a?'on':''}" onclick="state.area='${a}';render()">${a}<span class="c">${c}</span></button>`;
  }).join('');

  // Tiers
  document.getElementById('tiers').innerHTML=['all','house','upstream'].map(t=>{
    const c=t==='all'?S.length:S.filter(s=>s.tier===t).length;
    return `<button class="rail-btn ${state.tier===t?'on':''}" onclick="state.tier='${t}';render()">${t==='all'?'all':t==='house'?'house copies':'upstream'}<span class="c">${c}</span></button>`;
  }).join('');

  // Sources
  const srcs={};S.forEach(s=>{const k=s.source.split('/')[0];srcs[k]=(srcs[k]||0)+1;});
  document.getElementById('sources').innerHTML=`<button class="rail-btn ${state.source==='all'?'on':''}" onclick="state.source='all';render()">all<span class="c">${S.length}</span></button>`+
    Object.entries(srcs).sort((a,b)=>b[1]-a[1]).map(([k,v])=>`<button class="rail-btn ${state.source===k?'on':''}" onclick="state.source='${k}';render()">${k}<span class="c">${v}</span></button>`).join('');

  // Table
  const items=filtered();
  const tb=document.getElementById('tbody');
  if(items.length===0){tb.innerHTML='<tr><td colspan="6"><div class="empty"><p>No skills match these filters.</p></div></td></tr>';return;}
  tb.innerHTML=items.map(s=>`<tr class="${state.selected===s.name?'selected':''}">
    <td><span class="tier-dot ${s.tier}"></span></td>
    <td><span class="skill-name" onclick="openDrawer('${s.name}')">${s.name}</span></td>
    <td><span class="area-tag">${s.area} / ${s.branch}</span></td>
    <td><span class="src-tag">${s.source.split('/')[0]}</span></td>
    <td><span class="trust-tag ${s.trust}">${s.trust}</span></td>
    <td><div class="acts">
      <button class="act" onclick="openDrawer('${s.name}')" title="Inspect">inspect</button>
      <button class="act" onclick="copyCmd('${s.name}')" title="Copy install">install</button>
      <button class="act danger" onclick="confirmRemove('${s.name}')" title="Remove">remove</button>
    </div></td>
  </tr>`).join('');
}

function openDrawer(name){
  state.selected=name;
  const s=S.find(x=>x.name===name);
  if(!s)return;
  const d=document.getElementById('drawer');
  const isH=s.tier==='house';
  const steps=isH?[
    'Check local skills/'+s.name+'/',
    'Copy SKILL.md to ~/.claude/skills/'+s.name+'/',
    'Skill active immediately',
  ]:[
    'Look up '+s.name+' in skills.json',
    'git clone --depth 1 '+s.source,
    'Extract '+s.name+' from clone',
    'Copy to ~/.claude/skills/'+s.name+'/',
    'Clean up. Done.',
  ];
  d.innerHTML=`<button class="drawer-close" onclick="closeDrawer()">×</button>
    <h2>${s.name}</h2>
    <div class="subtitle">${isH?'House copy':'Cataloged upstream'} · ${s.area} / ${s.branch}</div>
    <div class="drawer-field"><div class="k">Source</div><div class="v">${s.source}</div></div>
    <div class="drawer-field"><div class="k">Trust</div><div class="v">${s.trust}</div></div>
    <div class="drawer-field"><div class="k">Tier</div><div class="v">${isH?'Vendored local folder. Fast, offline, you own the content.':'Metadata in skills.json. Install pulls live from GitHub.'}</div></div>
    <div class="drawer-sep"></div>
    <div class="drawer-field"><div class="k">Install Flow</div><div class="flow-steps">${steps.map((st,i)=>`<div class="flow-step"><span class="n">${i+1}</span>${st}</div>`).join('')}</div></div>
    <div class="drawer-cmd">$ npx ai-agent-skills install ${s.name}<button class="cp" onclick="navigator.clipboard.writeText('npx ai-agent-skills install ${s.name}');toast('Copied')">copy</button></div>
    <div class="drawer-sep"></div>
    <div class="drawer-field"><div class="k">Actions</div></div>
    <div class="drawer-actions">
      <button class="drawer-act primary" onclick="copyCmd('${s.name}')">Copy install</button>
      <button class="drawer-act" onclick="toast('Moved to area picker')">Move area</button>
      <button class="drawer-act" onclick="toast('Trust updated')">Change trust</button>
      <button class="drawer-act" onclick="toast('Converted to ${isH?'upstream':'house copy'}')">Convert to ${isH?'upstream':'house'}</button>
      <button class="drawer-act" onclick="toast('Archived: ${s.name}')">Archive</button>
      <button class="drawer-act danger" onclick="confirmRemove('${s.name}')">Remove from catalog</button>
    </div>`;
  d.classList.add('open');
  render();
}

function closeDrawer(){document.getElementById('drawer').classList.remove('open');state.selected=null;render();}
function copyCmd(name){navigator.clipboard.writeText('npx ai-agent-skills install '+name);toast('Copied: npx ai-agent-skills install '+name);}
function confirmRemove(name){if(confirm('Remove '+name+' from the catalog?')){const i=S.findIndex(s=>s.name===name);if(i>-1){S.splice(i,1);closeDrawer();render();toast('Removed: '+name);}}}

document.getElementById('search').addEventListener('input',e=>{state.query=e.target.value;render();});
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeDrawer();});
render();
</script>
</body>
</html>


================================================
FILE: cli.js
================================================
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const os = require('os');
const readline = require('readline');
const { pathToFileURL } = require('url');
const { compareSkillsByCurationData, getGitHubInstallSpec, getSiblingRecommendations, sortSkillsByCuration } = require('./tui/catalog.cjs');
const {
  AGENT_PATHS,
  CONFIG_FILE,
  LEGACY_AGENTS,
  MAX_SKILL_SIZE,
  SCOPES,
} = require('./lib/paths.cjs');
const {
  createLibraryContext,
  getBundledLibraryContext,
  isManagedWorkspaceRoot,
  resolveLibraryContext,
  readWorkspaceConfig,
} = require('./lib/library-context.cjs');
const {
  addSkillToCollections,
  addUpstreamSkillFromDiscovery,
  applyCurateChanges,
  buildReviewQueue,
  buildHouseCatalogEntry,
  buildUpstreamCatalogEntry,
  commitCatalogData,
  curateSkill,
  ensureCollectionIdsExist,
  removeSkillFromCatalog,
  normalizeListInput,
  ensureRequiredPlacement,
  addHouseSkillEntry,
  currentIsoDay,
  currentCatalogTimestamp,
} = require('./lib/catalog-mutations.cjs');
const {
  findSkillByName,
  loadCatalogData,
  normalizeSkill,
} = require('./lib/catalog-data.cjs');
const { buildDependencyGraph, resolveInstallOrder } = require('./lib/dependency-graph.cjs');
const { buildInstallStateIndex, formatInstallStateLabel, getInstallState, getInstalledSkillNames, listInstalledSkillNamesInDir } = require('./lib/install-state.cjs');
const { README_MARKERS, generatedDocsAreInSync, renderGeneratedDocs, writeGeneratedDocs } = require('./lib/render-docs.cjs');
const { parseSkillMarkdown: parseSkillMarkdownFile } = require('./lib/frontmatter.cjs');
const { readInstalledMeta, writeInstalledMeta } = require('./lib/install-metadata.cjs');
const {
  getCatalogSkillRelativePath,
  hasLocalCatalogSkillFiles,
  resolveCatalogSkillSourcePath,
  shouldTreatCatalogSkillAsHouse,
} = require('./lib/catalog-paths.cjs');
const {
  buildImportedWhyHere,
  buildWorkAreaDistribution,
  classifyImportedSkill,
  discoverImportCandidates,
  inferImportedBranch,
} = require('./lib/workspace-import.cjs');
const {
  classifyGitError: classifyGitErrorLib,
  discoverSkills: discoverSkillsLib,
  expandPath: expandPathLib,
  getRepoNameFromUrl: getRepoNameFromUrlLib,
  isGitUrl: isGitUrlLib,
  isLocalPath: isLocalPathLib,
  isWindowsPath: isWindowsPathLib,
  parseGitUrl: parseGitUrlLib,
  parseSource: parseSourceLib,
  prepareSource: prepareSourceLib,
  sanitizeGitUrl: sanitizeGitUrlLib,
  sanitizeSubpath: sanitizeSubpathLib,
  validateGitUrl: validateGitUrlLib,
} = require('./lib/source.cjs');

// Security posture: The agent is not a trusted operator.
// All inputs are validated, outputs are sandboxed to the working directory or
// install target, and skill content is sanitized before display. Never trust
// agent-supplied paths, identifiers, or payloads without validation.

// Version check
const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(Number);
if (NODE_MAJOR < 14 || (NODE_MAJOR === 14 && NODE_MINOR < 16)) {
  console.error(`Error: Node.js 14.16+ required (you have ${process.versions.node})`);
  process.exit(1);
}

const colors = {
  reset: '\x1b[0m',
  bold: '\x1b[1m',
  dim: '\x1b[2m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  cyan: '\x1b[36m',
  red: '\x1b[31m',
  magenta: '\x1b[35m'
};

const LEGACY_COLLECTION_ALIASES = {
  'web-product': {
    targetId: 'build-apps',
    message: 'Collection "web-product" now maps to "build-apps".'
  },
  'mobile-expo': {
    targetId: 'build-apps',
    message: 'Collection "mobile-expo" now maps to "build-apps". Use tags like "expo" when you want the mobile slice.'
  },
  'backend-systems': {
    targetId: 'build-systems',
    message: 'Collection "backend-systems" now maps to "build-systems".'
  },
  'quality-workflows': {
    targetId: 'test-and-debug',
    message: 'Collection "quality-workflows" now maps to "test-and-debug".'
  },
  'docs-files': {
    targetId: 'docs-and-research',
    message: 'Collection "docs-files" now maps to "docs-and-research".'
  },
  'business-research': {
    targetId: null,
    message: 'Collection "business-research" is no longer a top-level collection. Use search or tags for those skills.'
  },
  'creative-media': {
    targetId: null,
    message: 'Collection "creative-media" is no longer a top-level collection. Use search or tags for those skills.'
  }
};

const SWIFT_SHORTCUT = 'swift';
const MKTG_SHORTCUT = 'mktg';
const UNIVERSAL_DEFAULT_AGENTS = ['claude', 'codex'];
const FORMAT_ENUM = ['text', 'json'];
const WORK_AREA_ENUM = ['frontend', 'backend', 'mobile', 'workflow', 'agent-engineering', 'marketing'];
const CATEGORY_ENUM = ['development', 'document', 'creative', 'business', 'productivity'];
const TRUST_ENUM = ['listed', 'verified'];
const TIER_ENUM = ['house', 'upstream'];
const DISTRIBUTION_ENUM = ['bundled', 'live'];
const ORIGIN_ENUM = ['authored', 'curated', 'adapted'];
const SYNC_MODE_ENUM = ['snapshot', 'live', 'authored', 'adapted'];

const FLAG_DEFINITIONS = {
  format: { type: 'enum', enum: FORMAT_ENUM, default: null, description: 'Output format.' },
  project: { type: 'boolean', alias: '-p', default: false, description: 'Target project scope.' },
  global: { type: 'boolean', alias: '-g', default: false, description: 'Target global scope.' },
  skill: { type: 'string[]', default: [], description: 'Select named skills from a source.' },
  list: { type: 'boolean', default: false, description: 'List skills without installing or mutating.' },
  yes: { type: 'boolean', alias: '-y', default: false, description: 'Skip interactive confirmation.' },
  all: { type: 'boolean', default: false, description: 'Apply to both global and project scope.' },
  dryRun: { type: 'boolean', alias: '-n', default: false, description: 'Show what would happen without changing files.' },
  noDeps: { type: 'boolean', default: false, description: 'Skip dependency expansion for catalog installs.' },
  agent: { type: 'string', alias: '-a', default: null, description: 'Legacy explicit agent target.' },
  agents: { type: 'string[]', default: [], description: 'Legacy explicit agent targets.' },
  installed: { type: 'boolean', alias: '-i', default: false, description: 'Show installed skills instead of the catalog.' },
  category: { type: 'enum', alias: '-c', enum: CATEGORY_ENUM, default: null, description: 'Filter by category.' },
  area: { type: 'string', default: null, description: 'Filter or place a skill into a work area shelf.' },
  areas: { type: 'string', default: null, description: 'Comma-separated work area ids for init-library.' },
  collection: { type: 'string', default: null, description: 'Filter or target a curated collection.' },
  removeFromCollection: { type: 'string', default: null, description: 'Remove a skill from a curated collection.' },
  tags: { type: 'string', alias: '-t', default: null, description: 'Comma-separated tags.' },
  labels: { type: 'string', default: null, description: 'Comma-separated labels.' },
  notes: { type: 'string', default: null, description: 'Curator notes.' },
  why: { type: 'string', default: null, description: 'Why the skill belongs in the library.' },
  branch: { type: 'string', default: null, description: 'Shelf branch label.' },
  trust: { type: 'enum', enum: TRUST_ENUM, default: null, description: 'Trust level.' },
  description: { type: 'string', default: null, description: 'Skill description override.' },
  lastVerified: { type: 'string', default: null, description: 'Last verification date.' },
  feature: { type: 'boolean', default: false, description: 'Mark a skill as featured.' },
  unfeature: { type: 'boolean', default: false, description: 'Remove featured state.' },
  verify: { type: 'boolean', default: false, description: 'Mark a skill as verified.' },
  clearVerified: { type: 'boolean', default: false, description: 'Clear verified state.' },
  remove: { type: 'boolean', default: false, description: 'Remove a skill from the catalog.' },
  json: { type: 'boolean', default: false, description: 'Help/describe: emit schema JSON. Mutations: read a JSON payload from stdin.' },
  fields: { type: 'string', default: null, description: 'Comma-separated field mask for JSON read output.' },
  limit: { type: 'integer', default: null, description: 'Limit JSON read results.' },
  offset: { type: 'integer', default: null, description: 'Offset JSON read results.' },
  import: { type: 'boolean', default: false, description: 'Import discovered skills into a workspace.' },
  autoClassify: { type: 'boolean', default: false, description: 'Attempt heuristic work area assignment during import.' },
};

const COMMAND_REGISTRY = {
  browse: {
    aliases: ['b'],
    summary: 'Browse the library in the terminal.',
    args: [],
    flags: ['project', 'global', 'agent', 'format'],
  },
  [SWIFT_SHORTCUT]: {
    aliases: [],
    summary: 'Install the curated Swift hub.',
    args: [],
    flags: ['project', 'global', 'all', 'list', 'dryRun', 'format'],
  },
  [MKTG_SHORTCUT]: {
    aliases: ['marketing-cli'],
    summary: 'Install the curated mktg marketing pack.',
    args: [],
    flags: ['project', 'global', 'all', 'list', 'dryRun', 'format'],
  },
  list: {
    aliases: ['ls'],
    summary: 'List catalog skills.',
    args: [],
    flags: ['installed', 'category', 'tags', 'collection', 'area', 'project', 'global', 'fields', 'limit', 'offset', 'format'],
  },
  collections: {
    aliases: [],
    summary: 'Browse curated collections.',
    args: [],
    flags: ['fields', 'limit', 'offset', 'format'],
  },
  install: {
    aliases: ['i'],
    summary: 'Install skills from the library or an external source.',
    args: [{ name: 'source', required: false, type: 'string' }],
    flags: ['project', 'global', 'collection', 'skill', 'list', 'yes', 'all', 'dryRun', 'noDeps', 'agent', 'agents', 'fields', 'limit', 'offset', 'format'],
  },
  add: {
    aliases: [],
    summary: 'Add a bundled pick, upstream repo skill, or house copy to a workspace.',
    args: [{ name: 'source', required: true, type: 'string' }],
    flags: ['list', 'skill', 'area', 'branch', 'category', 'tags', 'labels', 'notes', 'trust', 'why', 'description', 'collection', 'lastVerified', 'feature', 'clearVerified', 'remove', 'dryRun', 'json', 'format'],
  },
  uninstall: {
    aliases: ['remove', 'rm'],
    summary: 'Remove an installed skill.',
    args: [{ name: 'name', required: true, type: 'string' }],
    flags: ['project', 'global', 'agent', 'agents', 'dryRun', 'json', 'format'],
  },
  sync: {
    aliases: ['update', 'upgrade'],
    summary: 'Refresh installed skills.',
    args: [{ name: 'name', required: false, type: 'string' }],
    flags: ['all', 'project', 'global', 'agent', 'agents', 'dryRun', 'format'],
  },
  search: {
    aliases: ['s', 'find'],
    summary: 'Search the catalog.',
    args: [{ name: 'query', required: true, type: 'string' }],
    flags: ['category', 'collection', 'area', 'fields', 'limit', 'offset', 'format'],
  },
  info: {
    aliases: ['show'],
    summary: 'Show skill details and provenance.',
    args: [{ name: 'name', required: true, type: 'string' }],
    flags: ['fields', 'format'],
  },
  preview: {
    aliases: [],
    summary: 'Preview a skill body or upstream summary.',
    args: [{ name: 'name', required: true, type: 'string' }],
    flags: ['fields', 'format'],
  },
  catalog: {
    aliases: [],
    summary: 'Add upstream skills to the catalog without vendoring files.',
    args: [{ name: 'repo', required: true, type: 'string' }],
    flags: ['list', 'skill', 'area', 'branch', 'category', 'tags', 'labels', 'notes', 'trust', 'why', 'description', 'collection', 'dryRun', 'json', 'format'],
  },
  curate: {
    aliases: [],
    summary: 'Edit catalog metadata and placement.',
    args: [{ name: 'name', required: true, type: 'string' }],
    flags: ['area', 'branch', 'category', 'tags', 'labels', 'notes', 'trust', 'why', 'description', 'collection', 'removeFromCollection', 'feature', 'unfeature', 'verify', 'clearVerified', 'remove', 'yes', 'dryRun', 'json', 'format'],
  },
  vendor: {
    aliases: [],
    summary: 'Create a house copy from an explicit source.',
    args: [{ name: 'source', required: true, type: 'string' }],
    flags: ['list', 'skill', 'area', 'branch', 'category', 'tags', 'labels', 'notes', 'trust', 'why', 'description', 'collection', 'lastVerified', 'feature', 'clearVerified', 'remove', 'dryRun', 'json', 'format'],
  },
  check: {
    aliases: [],
    summary: 'Check installed skills for potential updates.',
    args: [],
    flags: ['project', 'global', 'format'],
  },
  doctor: {
    aliases: [],
    summary: 'Diagnose install issues.',
    args: [],
    flags: ['agent', 'agents', 'format'],
  },
  validate: {
    aliases: [],
    summary: 'Validate a skill directory.',
    args: [{ name: 'path', required: false, type: 'string' }],
    flags: ['format'],
  },
  init: {
    aliases: [],
    summary: 'Create a new SKILL.md template.',
    args: [{ name: 'name', required: false, type: 'string' }],
    flags: ['dryRun', 'format'],
  },
  'init-library': {
    aliases: [],
    summary: 'Create a managed library workspace.',
    args: [{ name: 'name', required: true, type: 'string' }],
    flags: ['areas', 'import', 'autoClassify', 'dryRun', 'json', 'format'],
  },
  import: {
    aliases: [],
    summary: 'Import local skills into the active managed workspace.',
    args: [{ name: 'path', required: false, type: 'string' }],
    flags: ['autoClassify', 'dryRun', 'format'],
  },
  'build-docs': {
    aliases: [],
    summary: 'Regenerate README.md and WORK_AREAS.md in a workspace.',
    args: [],
    flags: ['dryRun', 'format'],
  },
  config: {
    aliases: [],
    summary: 'Manage CLI settings.',
    args: [],
    flags: ['format'],
  },
  help: {
    aliases: ['--help', '-h'],
    summary: 'Show CLI help.',
    args: [{ name: 'command', required: false, type: 'string' }],
    flags: ['format', 'json'],
  },
  describe: {
    aliases: [],
    summary: 'Show machine-readable schema for one command.',
    args: [{ name: 'command', required: true, type: 'string' }],
    flags: ['format', 'json'],
  },
  version: {
    aliases: ['--version', '-v'],
    summary: 'Show CLI version.',
    args: [],
    flags: ['format'],
  },
};

const COMMAND_ALIAS_MAP = Object.entries(COMMAND_REGISTRY).reduce((map, [name, definition]) => {
  map.set(name, name);
  for (const alias of definition.aliases || []) {
    map.set(alias, name);
  }
  return map;
}, new Map());

function resolveCommandAlias(command) {
  return COMMAND_ALIAS_MAP.get(command) || command;
}

function getCommandDefinition(command) {
  const canonical = resolveCommandAlias(command);
  return COMMAND_REGISTRY[canonical] || null;
}

function getFlagSchema(flagName) {
  const definition = FLAG_DEFINITIONS[flagName];
  if (!definition) return null;
  return {
    name: flagName,
    ...definition,
  };
}

function stringSchema(description = null, extra = {}) {
  return {
    type: 'string',
    ...(description ? { description } : {}),
    ...extra,
  };
}

function booleanSchema(description = null, extra = {}) {
  return {
    type: 'boolean',
    ...(description ? { description } : {}),
    ...extra,
  };
}

function integerSchema(description = null, extra = {}) {
  return {
    type: 'integer',
    ...(description ? { description } : {}),
    ...extra,
  };
}

function enumSchema(values, description = null, extra = {}) {
  return {
    type: 'string',
    enum: values,
    ...(description ? { description } : {}),
    ...extra,
  };
}

function arraySchema(items, description = null, extra = {}) {
  return {
    type: 'array',
    items,
    ...(description ? { description } : {}),
    ...extra,
  };
}

function objectSchema(properties, required = [], description = null, extra = {}) {
  return {
    type: 'object',
    properties,
    required,
    additionalProperties: false,
    ...(description ? { description } : {}),
    ...extra,
  };
}

function oneOfSchema(variants, description = null, extra = {}) {
  return {
    oneOf: variants,
    ...(description ? { description } : {}),
    ...extra,
  };
}

function nullableSchema(schema) {
  return {
    ...schema,
    nullable: true,
  };
}

function buildEnvelopeSchema(commandName, dataSchema, description = null) {
  return {
    format: 'json-envelope',
    schema: objectSchema({
      command: stringSchema('Resolved command name.', { const: resolveCommandAlias(commandName) }),
      status: enumSchema(['ok', 'error'], 'Command status.'),
      data: dataSchema,
      errors: arraySchema(
        objectSchema({
          code: stringSchema('Stable machine-readable error code.'),
          message: stringSchema('Human-readable error message.'),
          hint: nullableSchema(stringSchema('Optional recovery hint.')),
        }, ['code', 'message']),
        'Structured errors.'
      ),
    }, ['command', 'status', 'data', 'errors'], description),
  };
}

function buildNdjsonSchema(commandName, summarySchema, itemSchema, description = null, extraKinds = {}) {
  return {
    format: 'ndjson',
    stream: true,
    recordSchema: objectSchema({
      command: stringSchema('Resolved command name.', { const: resolveCommandAlias(commandName) }),
      status: enumSchema(['ok', 'error'], 'Command status.'),
      data: objectSchema({
        kind: stringSchema('Record type discriminator.'),
      }, ['kind'], 'Per-record payload.'),
      errors: arraySchema(
        objectSchema({
          code: stringSchema('Stable machine-readable error code.'),
          message: stringSchema('Human-readable error message.'),
          hint: nullableSchema(stringSchema('Optional recovery hint.')),
        }, ['code', 'message'])
      ),
    }, ['command', 'status', 'data', 'errors'], description),
    records: {
      summary: summarySchema,
      item: itemSchema,
      ...extraKinds,
    },
  };
}

const STRING_OR_STRING_ARRAY_SCHEMA = oneOfSchema([
  stringSchema('Comma-separated string form.'),
  arraySchema(stringSchema('Individual value.'), 'Array form.'),
], 'Accepts either a comma-separated string or an array of strings.');

const COLLECTION_INPUT_SCHEMA = oneOfSchema([
  stringSchema('Collection id.'),
  arraySchema(stringSchema('Collection id.'), 'Collection ids.'),
], 'Accepts one collection id or an array of collection ids.');

const WORK_AREA_INPUT_SCHEMA = oneOfSchema([
  stringSchema('Work area id.'),
  objectSchema({
    id: stringSchema('Work area id.'),
    title: stringSchema('Display title.'),
    description: stringSchema('Optional description.'),
  }, ['id']),
], 'Accepts a work area id or a full work area object.');

const STARTER_COLLECTION_INPUT_SCHEMA = oneOfSchema([
  stringSchema('Collection id.'),
  objectSchema({
    id: stringSchema('Collection id.'),
    title: stringSchema('Display title.'),
    description: stringSchema('Optional description.'),
    skills: arraySchema(stringSchema('Skill name.'), 'Optional starter skill ids.'),
  }, ['id']),
], 'Accepts a collection id or a full collection object.');

const SERIALIZED_SKILL_SCHEMA = objectSchema({
  name: stringSchema('Skill name.'),
  description: stringSchema('Skill description after sanitization.'),
  workArea: nullableSchema(stringSchema('Work area id.')),
  branch: nullableSchema(stringSchema('Branch label.')),
  category: nullableSchema(stringSchema('Category id.')),
  tier: enumSchema(TIER_ENUM, 'Catalog tier.'),
  distribution: enumSchema(DISTRIBUTION_ENUM, 'Distribution mode.'),
  source: nullableSchema(stringSchema('Source repo or source reference.')),
  installSource: nullableSchema(stringSchema('Install source reference.')),
  trust: nullableSchema(stringSchema('Trust level.')),
  origin: nullableSchema(stringSchema('Origin label.')),
  featured: booleanSchema('Featured flag.'),
  verified: booleanSchema('Verified flag.'),
  tags: arraySchema(stringSchema('Tag.')),
  collections: arraySchema(stringSchema('Collection id.')),
  installState: nullableSchema(stringSchema('Install state label.')),
  whyHere: stringSchema('Curator note after sanitization.'),
}, ['name', 'description', 'tier', 'distribution', 'featured', 'verified', 'tags', 'collections', 'whyHere']);

function buildMutationStdinSchema(commandName) {
  if (commandName === 'init-library') {
    return objectSchema({
      name: stringSchema('Library name.'),
      workAreas: arraySchema(WORK_AREA_INPUT_SCHEMA, 'Optional custom starter work areas.'),
      collections: arraySchema(STARTER_COLLECTION_INPUT_SCHEMA, 'Optional starter collections.'),
      import: booleanSchema('Import discovered skills immediately after bootstrap.'),
      autoClassify: booleanSchema('Attempt heuristic work area assignment during import.'),
      dryRun: booleanSchema('Preview without writing files.'),
    }, ['name'], 'Read from stdin when `--json` is passed.');
  }

  if (commandName === 'uninstall') {
    return objectSchema({
      name: stringSchema('Installed skill name to remove.'),
      dryRun: booleanSchema('Preview without deleting files.'),
    }, ['name'], 'Read from stdin when `--json` is passed.');
  }

  if (commandName === 'curate') {
    return objectSchema({
      name: stringSchema('Catalog skill name to edit.'),
      workArea: stringSchema('Work area shelf id.'),
      branch: stringSchema('Branch label.'),
      category: enumSchema(CATEGORY_ENUM, 'Category id.'),
      tags: STRING_OR_STRING_ARRAY_SCHEMA,
      labels: STRING_OR_STRING_ARRAY_SCHEMA,
      notes: stringSchema('Curator notes.'),
      trust: enumSchema(TRUST_ENUM, 'Trust level.'),
      whyHere: stringSchema('Why the skill belongs in the library.'),
      description: stringSchema('Description override.'),
      collections: COLLECTION_INPUT_SCHEMA,
      removeFromCollection: stringSchema('Collection id to remove membership from.'),
      featured: booleanSchema('Mark as featured.'),
      clearVerified: booleanSchema('Clear verified flag.'),
      remove: booleanSchema('Remove the skill from the catalog.'),
      yes: booleanSchema('Skip confirmation for destructive actions.'),
      dryRun: booleanSchema('Preview the edit without writing files.'),
    }, ['name'], 'Read from stdin when `--json` is passed.');
  }

  if (commandName === 'add' || commandName === 'catalog' || commandName === 'vendor') {
    return objectSchema({
      source: stringSchema(commandName === 'add'
        ? 'Bundled skill name, GitHub repo, git URL, or local path.'
        : 'GitHub repo, git URL, or local path.'),
      name: stringSchema('Skill name or fallback selector when the source is a bundled catalog entry.'),
      skill: stringSchema('Explicit discovered skill name inside the source.'),
      list: booleanSchema('List discovered skills without mutating the workspace.'),
      workArea: stringSchema('Work area shelf id from skills.json.'),
      branch: stringSchema('Branch label from skills.json.'),
      category: enumSchema(CATEGORY_ENUM, 'Category id from skills.json.'),
      tags: STRING_OR_STRING_ARRAY_SCHEMA,
      labels: STRING_OR_STRING_ARRAY_SCHEMA,
      notes: stringSchema('Curator notes.'),
      trust: enumSchema(TRUST_ENUM, 'Trust level.'),
      whyHere: stringSchema('Curator note stored as `whyHere` in skills.json.'),
      description: stringSchema('Description override stored in skills.json.'),
      collections: COLLECTION_INPUT_SCHEMA,
      lastVerified: stringSchema('Last verification date.'),
      featured: booleanSchema('Mark as featured.'),
      clearVerified: booleanSchema('Clear verified flag.'),
      remove: booleanSchema('Remove the matching catalog entry.'),
      ref: stringSchema('Optional Git ref for upstream sources.'),
      dryRun: booleanSchema('Preview the mutation without writing files.'),
    }, commandName === 'add' ? [] : ['source'], 'Read from stdin when `--json` is passed. Field names match the editable skills.json entry shape.');
  }

  return null;
}

function buildCommandInputSchema(commandName) {
  const stdin = buildMutationStdinSchema(commandName);
  return {
    stdin,
  };
}

const IMPORT_RESULT_SCHEMA = objectSchema({
  rootDir: stringSchema('Import root directory.'),
  discoveredCount: integerSchema('Total discovered skill folders, including invalid-name candidates.'),
  importedCount: integerSchema('Imported skills.'),
  copiedCount: integerSchema('Copied skills.'),
  inPlaceCount: integerSchema('In-place imported skills.'),
  autoClassifiedCount: integerSchema('Auto-classified skills.'),
  fallbackWorkflowCount: integerSchema('Skills assigned to workflow as a fallback.'),
  needsCurationCount: integerSchema('Skills still needing manual review.'),
  skippedCount: integerSchema('All skipped candidates.'),
  skippedInvalidNameCount: integerSchema('Skipped invalid-name candidates.'),
  skippedDuplicateCount: integerSchema('Skipped duplicates.'),
  failedCount: integerSchema('Failed candidates.'),
  distribution: objectSchema({}, [], 'Imported skill counts by work area.'),
  imported: arraySchema(objectSchema({
    name: stringSchema('Skill name.'),
    path: stringSchema('Catalog path.'),
    workArea: stringSchema('Assigned work area.'),
    copied: booleanSchema('Whether files were copied into the workspace.'),
    autoClassified: booleanSchema('Whether work area was inferred heuristically.'),
    needsCuration: booleanSchema('Whether the skill should be reviewed manually.'),
  }, ['name', 'path', 'workArea', 'copied', 'autoClassified', 'needsCuration'])),
  skipped: arraySchema(objectSchema({
    name: nullableSchema(stringSchema('Skill name.')),
    path: stringSchema('Original path.'),
    reason: stringSchema('Skip reason.'),
  }, ['path', 'reason'])),
  skippedInvalidNames: arraySchema(objectSchema({
    name: nullableSchema(stringSchema('Skill name.')),
    path: stringSchema('Original path.'),
    reason: stringSchema('Invalid-name reason.'),
  }, ['path', 'reason'])),
  skippedDuplicates: arraySchema(objectSchema({
    name: nullableSchema(stringSchema('Skill name.')),
    path: stringSchema('Original path.'),
    reason: stringSchema('Duplicate-skip reason.'),
  }, ['path', 'reason'])),
  failures: arraySchema(objectSchema({
    path: stringSchema('Original path.'),
    reason: stringSchema('Failure reason.'),
  }, ['path', 'reason'])),
}, ['rootDir', 'discoveredCount', 'importedCount', 'copiedCount', 'inPlaceCount', 'autoClassifiedCount', 'fallbackWorkflowCount', 'needsCurationCount', 'skippedCount', 'skippedInvalidNameCount', 'skippedDuplicateCount', 'failedCount', 'distribution', 'imported', 'skipped', 'skippedInvalidNames', 'skippedDuplicates', 'failures']);

function buildCommandOutputSchema(commandName) {
  if (commandName === 'list') {
    return buildNdjsonSchema(
      'list',
      objectSchema({
        kind: enumSchema(['summary']),
        total: integerSchema('Total matching skills.'),
        returned: integerSchema('Returned skills after pagination.'),
        limit: nullableSchema(integerSchema('Requested page size.')),
        offset: integerSchema('Requested offset.'),
        fields: arraySchema(stringSchema('Requested field.')),
        filters: objectSchema({
          category: nullableSchema(stringSchema('Category filter.')),
          tags: nullableSchema(stringSchema('Tags filter.')),
          collection: nullableSchema(stringSchema('Collection filter.')),
          workArea: nullableSchema(stringSchema('Work area filter.')),
        }, []),
        collection: nullableSchema(objectSchema({
          id: stringSchema('Collection id.'),
          title: stringSchema('Collection title.'),
          description: stringSchema('Collection description.'),
        }, ['id', 'title', 'description'])),
      }, ['kind', 'total', 'returned', 'offset', 'fields', 'filters', 'collection']),
      objectSchema({
        kind: enumSchema(['item']),
        skill: SERIALIZED_SKILL_SCHEMA,
      }, ['kind', 'skill']),
      'One record per line in JSON mode.'
    );
  }

  if (commandName === 'search') {
    return buildNdjsonSchema(
      'search',
      objectSchema({
        kind: enumSchema(['summary']),
        query: stringSchema('Search query.'),
        total: integerSchema('Total matching skills.'),
        returned: integerSchema('Returned skills after pagination.'),
        limit: nullableSchema(integerSchema('Requested page size.')),
        offset: integerSchema('Requested offset.'),
        fields: arraySchema(stringSchema('Requested field.')),
        filters: objectSchema({
          category: nullableSchema(stringSchema('Category filter.')),
          collection: nullableSchema(stringSchema('Collection filter.')),
          workArea: nullableSchema(stringSchema('Work area filter.')),
        }, []),
        suggestions: arraySchema(stringSchema('Fuzzy suggestion.')),
      }, ['kind', 'query', 'total', 'returned', 'offset', 'fields', 'filters', 'suggestions']),
      objectSchema({
        kind: enumSchema(['item']),
        skill: SERIALIZED_SKILL_SCHEMA,
      }, ['kind', 'skill']),
      'One record per line in JSON mode.'
    );
  }

  if (commandName === 'collections') {
    return buildNdjsonSchema(
      'collections',
      objectSchema({
        kind: enumSchema(['summary']),
        total: integerSchema('Total collections.'),
      }, ['kind', 'total']),
      objectSchema({
        kind: enumSchema(['item']),
        collection: objectSchema({
          id: stringSchema('Collection id.'),
          title: stringSchema('Collection title.'),
          description: stringSchema('Collection description.'),
          skillCount: integerSchema('Number of skills in the collection.'),
          installedCount: integerSchema('Installed skills in the collection.'),
          startHere: arraySchema(stringSchema('Recommended first skill.')),
          skills: arraySchema(stringSchema('Skill name.')),
        }, ['id', 'title', 'description', 'skillCount', 'installedCount', 'startHere', 'skills']),
      }, ['kind', 'collection']),
      'One record per line in JSON mode.'
    );
  }

  if (commandName === 'info') {
    return buildEnvelopeSchema(
      'info',
      objectSchema({
        name: stringSchema('Requested skill name.'),
        description: stringSchema('Skill description.'),
        fields: arraySchema(stringSchema('Requested top-level field.'), 'Present only when `--fields` is used.', { nullable: true }),
        skill: objectSchema({
          ...SERIALIZED_SKILL_SCHEMA.properties,
          sourceUrl: nullableSchema(stringSchema('Canonical source URL.')),
          syncMode: stringSchema('Sync mode.'),
          author: nullableSchema(stringSchema('Author.')),
          license: nullableSchema(stringSchema('License.')),
          labels: arraySchema(stringSchema('Label.')),
          notes: stringSchema('Curator notes.'),
          lastVerified: nullableSchema(stringSchema('Last verification date.')),
          lastUpdated: nullableSchema(stringSchema('Last updated date.')),
        }, ['syncMode', 'labels', 'notes']),
        collections: arraySchema(objectSchema({
          id: stringSchema('Collection id.'),
          title: stringSchema('Collection title.'),
        }, ['id', 'title'])),
        dependencies: objectSchema({
          dependsOn: arraySchema(stringSchema('Dependency skill.')),
          usedBy: arraySchema(stringSchema('Reverse dependency skill.')),
        }, ['dependsOn', 'usedBy']),
        neighboringShelfPicks: arraySchema(stringSchema('Nearby recommendation.')),
        installCommands: arraySchema(stringSchema('Ready-to-run install command.')),
      }, ['name', 'description', 'skill', 'collections', 'dependencies', 'neighboringShelfPicks', 'installCommands'])
    );
  }

  if (commandName === 'preview') {
    return buildEnvelopeSchema(
      'preview',
      objectSchema({
        name: stringSchema('Skill name.'),
        sourceType: enumSchema(['house', 'upstream'], 'Preview source type.'),
        path: nullableSchema(stringSchema('Local SKILL.md path for house copies.')),
        installSource: nullableSchema(stringSchema('Install source for upstream skills.')),
        content: nullableSchema(stringSchema('Sanitized preview body.')),
        sanitized: booleanSchema('Whether suspicious content was stripped.'),
      }, ['name', 'sourceType', 'content', 'sanitized'])
    );
  }

  if (commandName === 'install') {
    return {
      variants: [
        buildEnvelopeSchema('install', objectSchema({
          messages: arraySchema(objectSchema({
            level: stringSchema('Captured log level.'),
            message: stringSchema('Captured message.'),
          }, ['level', 'message'])),
        }, ['messages']), 'Default JSON envelope in non-streaming install flows.'),
        buildNdjsonSchema(
          'install',
          objectSchema({
            kind: enumSchema(['summary', 'plan']),
            source: nullableSchema(stringSchema('Remote workspace source when listing.')),
            total: nullableSchema(integerSchema('Total discovered skills when listing.')),
            requested: nullableSchema(integerSchema('Requested skills in a plan.')),
            resolved: nullableSchema(integerSchema('Resolved skills in a plan.')),
            targets: arraySchema(stringSchema('Install target path.'), 'Present for plan rows.', { nullable: true }),
          }, ['kind']),
          objectSchema({
            kind: enumSchema(['item', 'install']),
            skill: objectSchema({
              name: stringSchema('Skill name.'),
              tier: enumSchema(TIER_ENUM, 'Skill tier.'),
              workArea: nullableSchema(stringSchema('Work area when listing.')),
              branch: nullableSchema(stringSchema('Branch when listing.')),
              whyHere: nullableSchema(stringSchema('Curator note when listing.')),
              source: nullableSchema(stringSchema('Resolved source reference when planning.')),
            }, ['name', 'tier']),
          }, ['kind', 'skill']),
          'Streamed rows for remote workspace listing and parseable install plans.'
        ),
      ],
    };
  }

  if (commandName === 'help' || commandName === 'describe') {
    return buildEnvelopeSchema(
      commandName,
      objectSchema({
        binary: stringSchema('CLI binary name.'),
        version: stringSchema('CLI version.'),
        defaults: objectSchema({
          interactiveOutput: stringSchema('TTY default output format.'),
          nonTtyOutput: stringSchema('Non-TTY default output format.'),
        }, ['interactiveOutput', 'nonTtyOutput']),
        sharedEnums: objectSchema({
          format: arraySchema(stringSchema('Format value.')),
          workArea: arraySchema(stringSchema('Work area enum.')),
          category: arraySchema(stringSchema('Category enum.')),
          trust: arraySchema(stringSchema('Trust enum.')),
          tier: arraySchema(stringSchema('Tier enum.')),
          distribution: arraySchema(stringSchema('Distribution enum.')),
          origin: arraySchema(stringSchema('Origin enum.')),
          syncMode: arraySchema(stringSchema('Sync mode enum.')),
        }, ['format', 'workArea', 'category', 'trust', 'tier', 'distribution', 'origin', 'syncMode']),
        globalFlags: arraySchema(objectSchema({
          name: stringSchema('Flag name.'),
          type: stringSchema('Flag type.'),
        }, ['name', 'type'])),
        commands: arraySchema(objectSchema({
          name: stringSchema('Command name.'),
          summary: stringSchema('Command summary.'),
          inputSchema: objectSchema({
            stdin: nullableSchema(objectSchema({}, [])),
          }, []),
          outputSchema: objectSchema({}, []),
        }, ['name', 'summary', 'inputSchema', 'outputSchema']), 'Command schemas.'),
      }, ['binary', 'version', 'defaults', 'sharedEnums', 'globalFlags', 'commands'])
    );
  }

  if (commandName === 'init-library') {
    return {
      variants: [
        buildEnvelopeSchema('init-library', objectSchema({
          libraryName: stringSchema('Library name.'),
          librarySlug: stringSchema('Slugified directory name.'),
          targetDir: stringSchema('Workspace directory.'),
          files: objectSchema({
            config: stringSchema('Workspace config path.'),
            readme: stringSchema('README path.'),
            skillsJson: stringSchema('skills.json path.'),
            workAreas: stringSchema('WORK_AREAS.md path.'),
          }, ['config', 'readme', 'skillsJson', 'workAreas']),
          workAreas: arraySchema(stringSchema('Seeded work area id.')),
          import: nullableSchema(objectSchema({
            rootDir: stringSchema('Import root.'),
            discovered: integerSchema('Discovered skills.'),
            skipped: integerSchema('Skipped skills.'),
            failed: integerSchema('Failed candidates.'),
          }, ['rootDir', 'discovered', 'skipped', 'failed'])),
        }, ['libraryName', 'librarySlug', 'targetDir', 'files', 'workAreas'])),
        buildEnvelopeSchema('init-library', IMPORT_RESULT_SCHEMA, 'Returned when `init-library` chains directly into `--import`.'),
        buildEnvelopeSchema('init-library', objectSchema({
          dryRun: booleanSchema('Always true in this variant.', { const: true }),
          actions: arraySchema(objectSchema({
            type: stringSchema('Planned action type.'),
            target: stringSchema('Human-readable target.'),
            detail: nullableSchema(stringSchema('Action detail.')),
          }, ['type', 'target'])),
        }, ['dryRun', 'actions']), 'Dry-run response variant.'),
      ],
    };
  }

  if (commandName === 'import') {
    return buildEnvelopeSchema('import', IMPORT_RESULT_SCHEMA);
  }

  if (commandName === 'check') {
    return buildEnvelopeSchema('check', objectSchema({
      checked: integerSchema('Installed skills checked.'),
      updatesAvailable: integerSchema('Potential updates found.'),
      results: arraySchema(objectSchema({
        scope: stringSchema('Install scope.'),
        name: stringSchema('Skill name.'),
        status: stringSchema('Check result status.'),
        detail: stringSchema('Human-readable detail.'),
        sourceType: nullableSchema(stringSchema('Recorded source type.')),
      }, ['scope', 'name', 'status', 'detail', 'sourceType'])),
    }, ['checked', 'updatesAvailable', 'results']));
  }

  if (commandName === 'doctor') {
    return buildEnvelopeSchema('doctor', objectSchema({
      checks: arraySchema(objectSchema({
        name: stringSchema('Check name.'),
        ok: booleanSchema('Pass/fail.'),
        detail: stringSchema('Check detail.'),
      }, ['name', 'ok', 'detail'])),
      summary: objectSchema({
        passed: integerSchema('Passed checks.'),
        failed: integerSchema('Failed checks.'),
      }, ['passed', 'failed']),
    }, ['checks', 'summary']));
  }

  if (commandName === 'validate') {
    return buildEnvelopeSchema('validate', objectSchema({
      ok: booleanSchema('Validation result.'),
      summary: objectSchema({
        name: stringSchema('Skill name.'),
      }, ['name']),
      warnings: arraySchema(stringSchema('Validation warning.')),
    }, ['ok', 'summary', 'warnings']));
  }

  if (commandName === 'build-docs') {
    return buildEnvelopeSchema('build-docs', objectSchema({
      readmePath: stringSchema('README path.'),
      workAreasPath: stringSchema('WORK_AREAS.md path.'),
    }, ['readmePath', 'workAreasPath']));
  }

  if (commandName === 'config') {
    return buildEnvelopeSchema('config', objectSchema({
      path: stringSchema('Resolved config path.'),
      config: objectSchema({}, []),
    }, ['path', 'config']));
  }

  if (commandName === 'version') {
    return buildEnvelopeSchema('version', objectSchema({
      version: stringSchema('CLI version.'),
    }, ['version']));
  }

  if (['add', 'catalog', 'vendor', 'curate', 'uninstall', 'sync', 'browse', 'swift', 'init'].includes(commandName)) {
    return {
      variants: [
        buildEnvelopeSchema(commandName, objectSchema({
          messages: arraySchema(objectSchema({
            level: stringSchema('Captured log level.'),
            message: stringSchema('Captured message.'),
          }, ['level', 'message'])),
        }, ['messages'])),
        buildEnvelopeSchema(commandName, objectSchema({
          dryRun: booleanSchema('Always true in this variant.', { const: true }),
          actions: arraySchema(objectSchema({
            type: stringSchema('Planned action type.'),
            target: stringSchema('Human-readable target.'),
            detail: nullableSchema(stringSchema('Action detail.')),
          }, ['type', 'target'])),
        }, ['dryRun', 'actions']), 'Dry-run response variant when supported.'),
      ],
    };
  }

  return buildEnvelopeSchema(commandName, objectSchema({
    messages: arraySchema(objectSchema({
      level: stringSchema('Captured log level.'),
      message: stringSchema('Captured message.'),
    }, ['level', 'message'])),
  }, ['messages']));
}

function getCommandSchema(command) {
  const canonical = resolveCommandAlias(command);
  const definition = getCommandDefinition(canonical);
  if (!definition) return null;

  return {
    name: canonical,
    aliases: definition.aliases || [],
    summary: definition.summary,
    args: definition.args || [],
    flags: (definition.flags || [])
      .map((flagName) => getFlagSchema(flagName))
      .filter(Boolean),
    inputSchema: buildCommandInputSchema(canonical),
    outputSchema: buildCommandOutputSchema(canonical),
  };
}

function buildHelpSchema(command = null) {
  const pkg = require('./package.json');
  const selected = command ? resolveCommandAlias(command) : null;
  const commandSchema = selected ? getCommandSchema(selected) : null;

  return {
    binary: 'ai-agent-skills',
    version: pkg.version,
    defaults: {
      interactiveOutput: 'text',
      nonTtyOutput: 'json',
    },
    sharedEnums: {
      format: FORMAT_ENUM,
      workArea: WORK_AREA_ENUM,
      category: CATEGORY_ENUM,
      trust: TRUST_ENUM,
      tier: TIER_ENUM,
      distribution: DISTRIBUTION_ENUM,
      origin: ORIGIN_ENUM,
      syncMode: SYNC_MODE_ENUM,
    },
    globalFlags: ['format', 'json', 'project', 'global', 'agent', 'agents', 'dryRun']
      .map((flagName) => getFlagSchema(flagName))
      .filter(Boolean),
    commands: commandSchema
      ? [commandSchema]
      : Object.keys(COMMAND_REGISTRY).map((name) => getCommandSchema(name)),
  };
}

function emitSchemaHelp(command = null) {
  const schema = buildHelpSchema(command);
  emitJsonEnvelope('help', schema);
}

const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
let OUTPUT_STATE = {
  format: 'text',
  explicitFormat: false,
  command: null,
  emitted: false,
  data: null,
  messages: [],
  errors: [],
};

function stripAnsi(value) {
  return String(value == null ? '' : value).replace(ANSI_PATTERN, '');
}

function resolveOutputFormat(parsed = {}) {
  if (!parsed.format) return process.stdout.isTTY ? 'text' : 'json';
  if (!FORMAT_ENUM.includes(parsed.format)) {
    throw new Error(`Invalid format "${parsed.format}". Expected one of: ${FORMAT_ENUM.join(', ')}`);
  }
  return parsed.format;
}

function resetOutputState(format = 'text', command = null, explicitFormat = false) {
  OUTPUT_STATE = {
    format,
    explicitFormat,
    command,
    emitted: false,
    data: null,
    messages: [],
    errors: [],
  };
}

function isJsonOutput() {
  return OUTPUT_STATE.format === 'json';
}

function captureMessage(level, value) {
  const message = stripAnsi(value);
  OUTPUT_STATE.messages.push({ level, message });
  if (level === 'error') {
    OUTPUT_STATE.errors.push({ code: 'ERROR', message, hint: null });
  }
}

function log(msg) {
  if (isJsonOutput()) {
    captureMessage('log', msg);
    return;
  }
  console.log(msg);
}

function success(msg) {
  if (isJsonOutput()) {
    captureMessage('success', msg);
    return;
  }
  console.log(`${colors.green}${colors.bold}${msg}${colors.reset}`);
}

function info(msg) {
  if (isJsonOutput()) {
    captureMessage('info', msg);
    return;
  }
  console.log(`${colors.cyan}${msg}${colors.reset}`);
}

function warn(msg) {
  if (isJsonOutput()) {
    captureMessage('warn', msg);
    return;
  }
  console.log(`${colors.yellow}${msg}${colors.reset}`);
}

function error(msg) {
  if (isJsonOutput()) {
    captureMessage('error', msg);
    return;
  }
  console.log(`${colors.red}${msg}${colors.reset}`);
}

function setJsonResultData(data) {
  OUTPUT_STATE.data = data;
}

function emitJsonEnvelope(command, data = null, errors = null, options = {}) {
  const payload = {
    command: resolveCommandAlias(command || OUTPUT_STATE.command || 'help'),
    status: options.status || (process.exitCode ? 'error' : 'ok'),
    data: data != null ? data : (OUTPUT_STATE.data != null ? OUTPUT_STATE.data : { messages: OUTPUT_STATE.messages }),
    errors: errors != null ? errors : OUTPUT_STATE.errors,
  };
  console.log(JSON.stringify(payload, null, 2));
  OUTPUT_STATE.emitted = true;
}

function emitJsonRecord(command, data = null, errors = null, options = {}) {
  const payload = {
    command: resolveCommandAlias(command || OUTPUT_STATE.command || 'help'),
    status: options.status || (process.exitCode ? 'error' : 'ok'),
    data: data != null ? data : null,
    errors: errors != null ? errors : [],
  };
  console.log(JSON.stringify(payload));
  OUTPUT_STATE.emitted = true;
}

function finalizeJsonOutput() {
  if (!isJsonOutput() || OUTPUT_STATE.emitted) return;
  emitJsonEnvelope(OUTPUT_STATE.command);
}

function isMachineReadableOutput() {
  return isJsonOutput() || (!process.stdout.isTTY && !OUTPUT_STATE.explicitFormat);
}

function sanitizeMachineField(value) {
  return String(value == null ? '' : value)
    .replace(/\t/g, ' ')
    .replace(/\r?\n/g, ' ')
    .trim();
}

function emitMachineLine(kind, fields = []) {
  log([kind, ...fields.map(sanitizeMachineField)].join('\t'));
}

function emitActionableError(message, hint = '', options = {}) {
  if (isJsonOutput()) {
    OUTPUT_STATE.errors.push({
      code: options.code || 'ERROR',
      message: stripAnsi(message),
      hint: hint ? stripAnsi(hint) : null,
    });
    return;
  }

  if (options.machine || isMachineReadableOutput()) {
    emitMachineLine('ERROR', [options.code || 'ERROR', message]);
    if (hint) {
      emitMachineLine('HINT', [hint]);
    }
    return;
  }

  error(message);
  if (hint) {
    log(`${colors.dim}${hint}${colors.reset}`);
  }
}

function emitDryRunResult(command, actions = [], extra = {}) {
  if (isJsonOutput()) {
    setJsonResultData({
      dryRun: true,
      actions,
      ...extra,
    });
    return;
  }

  log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
  for (const action of actions) {
    const target = action.target ? `${action.target}` : action.type;
    const detail = action.detail ? ` ${colors.dim}${action.detail}${colors.reset}` : '';
    log(`  ${colors.green}${target}${colors.reset}${detail}`);
  }
}

let ACTIVE_LIBRARY_CONTEXT = getBundledLibraryContext();

function setActiveLibraryContext(context) {
  ACTIVE_LIBRARY_CONTEXT = context || getBundledLibraryContext();
  return ACTIVE_LIBRARY_CONTEXT;
}

function getActiveLibraryContext() {
  return ACTIVE_LIBRARY_CONTEXT || getBundledLibraryContext();
}

function getActiveSkillsDir() {
  return getActiveLibraryContext().skillsDir;
}

function getLibraryDisplayName(context = getActiveLibraryContext()) {
  if (context.mode === 'workspace') {
    const config = readWorkspaceConfig(context);
    return config?.libraryName || path.basename(context.rootDir);
  }
  return 'AI Agent Skills';
}

function getLibraryModeHint(context = getActiveLibraryContext()) {
  if (context.mode === 'workspace') {
    return `${colors.dim}Using workspace library at ${context.rootDir}${colors.reset}`;
  }
  return null;
}

function requireWorkspaceContext(actionLabel = 'This command') {
  const context = getActiveLibraryContext();
  if (context.mode !== 'workspace') {
    error(`${actionLabel} only works inside an initialized library workspace.`);
    log(`${colors.dim}Create one with: npx ai-agent-skills init-library <name>${colors.reset}`);
    process.exitCode = 1;
    return null;
  }
  return context;
}

function slugifyLibraryName(name) {
  return String(name || '')
    .toLowerCase()
    .replace(/[^a-z0-9-]/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '');
}

function isInsideDirectory(targetPath, candidatePath) {
  const relative = path.relative(path.resolve(targetPath), path.resolve(candidatePath));
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}

function isMaintainerRepoContext(context) {
  return context.mode === 'bundled'
    && fs.existsSync(path.join(context.rootDir, '.git'))
    && isInsideDirectory(context.rootDir, process.cwd());
}

function requireEditableLibraryContext(actionLabel = 'This command') {
  const context = getActiveLibraryContext();
  if (context.mode === 'workspace') {
    return context;
  }

  if (isMaintainerRepoContext(context)) {
    return context;
  }

  error(`${actionLabel} only works inside a managed workspace or the maintainer repo.`);
  log(`${colors.dim}Create one with: npx ai-agent-skills init-library <name>${colors.reset}`);
  process.exitCode = 1;
  return null;
}

function getCatalogContextFromMeta(meta) {
  if (!meta || !meta.libraryMode) {
    return getBundledLibraryContext();
  }

  if (meta.libraryMode === 'workspace') {
    if (meta.libraryRoot && isManagedWorkspaceRoot(meta.libraryRoot)) {
      return createLibraryContext(meta.libraryRoot, 'workspace');
    }

    const currentContext = resolveLibraryContext(process.cwd());
    if (currentContext.mode === 'workspace') {
      const currentConfig = readWorkspaceConfig(currentContext);
      const currentSlug = currentConfig?.librarySlug || path.basename(currentContext.rootDir);
      if (!meta.librarySlug || meta.librarySlug === currentSlug) {
        return currentContext;
      }
    }

    return null;
  }

  return getBundledLibraryContext();
}

function buildCatalogInstallMeta(skillName, targetDir, context = getActiveLibraryContext()) {
  const workspaceConfig = context.mode === 'workspace' ? readWorkspaceConfig(context) : null;
  return {
    sourceType: 'catalog',
    source: 'catalog',
    skillName,
    scope: resolveScopeLabel(targetDir),
    libraryMode: context.mode,
    libraryRoot: context.rootDir,
    librarySlug: workspaceConfig?.librarySlug || (context.mode === 'workspace' ? path.basename(context.rootDir) : null),
    libraryName: getLibraryDisplayName(context),
  };
}

function getBundledCatalogData() {
  return loadCatalogData(getBundledLibraryContext());
}

function getBundledCatalogSkill(skillName) {
  const bundledData = getBundledCatalogData();
  return bundledData.skills.find((skill) => skill.name === skillName) || null;
}

function inferInstallSourceFromCatalogSkill(skill) {
  if (!skill) return '';
  if (skill.installSource) return skill.installSource;
  if (!skill.source) return '';

  const normalizedPath = String(skill.path || '')
    .replace(/\\/g, '/')
    .replace(/^\/+/, '');

  if (!normalizedPath) return skill.source;
  return `${skill.source}/${normalizedPath}`;
}

function buildImportedCatalogEntryFromBundledSkill(skill, fields) {
  return normalizeSkill({
    name: skill.name,
    description: String(skill.description || '').trim(),
    category: String(fields.category || skill.category || 'development').trim(),
    workArea: String(fields.workArea || '').trim(),
    branch: String(fields.branch || '').trim(),
    author: String(skill.author || 'unknown').trim(),
    source: String(skill.source || '').trim(),
    license: String(skill.license || 'MIT').trim(),
    tier: 'upstream',
    distribution: 'live',
    vendored: false,
    installSource: inferInstallSourceFromCatalogSkill(skill),
    tags: Array.isArray(skill.tags) ? skill.tags : [],
    labels: Array.isArray(skill.labels) ? skill.labels : [],
    requires: Array.isArray(skill.requires) ? skill.requires : [],
    featured: false,
    verified: false,
    origin: 'curated',
    trust: String(fields.trust || 'listed').trim() || 'listed',
    syncMode: 'live',
    sourceUrl: String(skill.sourceUrl || '').trim(),
    whyHere: String(fields.whyHere || '').trim(),
    lastVerified: '',
    notes: String(fields.notes || '').trim(),
    addedDate: currentIsoDay(),
    lastCurated: currentCatalogTimestamp(),
  });
}

function getCatalogInstallOrder(data, requestedSkillNames, noDeps = false) {
  const names = Array.isArray(requestedSkillNames) ? requestedSkillNames : [requestedSkillNames];
  if (noDeps) {
    return [...new Set(names.filter(Boolean))];
  }
  return resolveInstallOrder(data, names);
}

function getCatalogInstallPlan(data, requestedSkillNames, noDeps = false) {
  const orderedNames = getCatalogInstallOrder(data, requestedSkillNames, noDeps);
  const requested = new Set((Array.isArray(requestedSkillNames) ? requestedSkillNames : [requestedSkillNames]).filter(Boolean));
  const skills = orderedNames
    .map((name) => findSkillByName(data, name))
    .filter(Boolean);

  return {
    orderedNames,
    requested,
    skills,
  };
}

function getInstallStateText(skillName, index = buildInstallStateIndex()) {
  return formatInstallStateLabel(getInstallState(index, skillName));
}

function serializeSkillForJson(data, skill, installStateIndex = null) {
  const safeDescription = sanitizeSkillContent(skill.description || '').content;
  const safeWhyHere = sanitizeSkillContent(skill.whyHere || '').content;
  return {
    name: skill.name,
    description: safeDescription,
    workArea: getSkillWorkArea(skill) || null,
    branch: getSkillBranch(skill) || null,
    category: skill.category || null,
    tier: getTier(skill),
    distribution: getDistribution(skill),
    source: skill.source || null,
    installSource: skill.installSource || null,
    trust: getTrust(skill),
    origin: getOrigin(skill),
    featured: !!skill.featured,
    verified: !!skill.verified,
    tags: Array.isArray(skill.tags) ? skill.tags : [],
    collections: getCollectionsForSkill(data, skill.name).map((collection) => collection.id),
    installState: installStateIndex ? (getInstallStateText(skill.name, installStateIndex) || null) : null,
    whyHere: safeWhyHere,
  };
}

const DEFAULT_LIST_JSON_FIELDS = ['name', 'tier', 'workArea', 'description'];
const DEFAULT_COLLECTIONS_JSON_FIELDS = ['id', 'title', 'description', 'skillCount', 'installedCount', 'startHere'];
const DEFAULT_PREVIEW_JSON_FIELDS = ['name', 'sourceType', 'content', 'sanitized'];
const DEFAULT_INSTALL_LIST_JSON_FIELDS = ['name', 'description'];
const DEFAULT_REMOTE_INSTALL_LIST_JSON_FIELDS = ['name', 'tier', 'workArea', 'branch', 'whyHere'];

function parseFieldMask(value, fallback = null) {
  if (value == null) return fallback;
  const fields = String(value)
    .split(',')
    .map((field) => field.trim())
    .filter(Boolean);
  return fields.length > 0 ? [...new Set(fields)] : fallback;
}

function selectObjectFields(record, fields) {
  if (!fields || fields.length === 0) return record;
  return fields.reduce((selected, field) => {
    if (Object.prototype.hasOwnProperty.call(record, field)) {
      selected[field] = record[field];
    }
    return selected;
  }, {});
}

function paginateItems(items, limit = null, offset = null) {
  const normalizedOffset = offset == null ? 0 : offset;
  const normalizedLimit = limit == null ? null : limit;
  const paged = normalizedLimit == null
    ? items.slice(normalizedOffset)
    : items.slice(normalizedOffset, normalizedOffset + normalizedLimit);

  return {
    items: paged,
    limit: normalizedLimit,
    offset: normalizedOffset,
    returned: paged.length,
    total: items.length,
  };
}

function applyTopLevelFieldMask(payload, fields, fallback = null) {
  const resolvedFields = parseFieldMask(fields, fallback);
  if (!resolvedFields || resolvedFields.length === 0) {
    return payload;
  }

  return {
    ...selectObjectFields(payload, resolvedFields),
    fields: resolvedFields,
  };
}

function resolveReadJsonOptions(parsed, commandName) {
  const fields = parseFieldMask(parsed.fields);
  const limit = parsed.limit;
  const offset = parsed.offset;

  if (limit != null && (!Number.isInteger(limit) || limit < 0)) {
    emitActionableError(
      `Invalid --limit value for ${commandName}.`,
      'Use a non-negative integer such as `--limit 10`.',
      { code: 'INVALID_LIMIT' }
    );
    process.exitCode = 1;
    return null;
  }

  if (offset != null && (!Number.isInteger(offset) || offset < 0)) {
    emitActionableError(
      `Invalid --offset value for ${commandName}.`,
      'Use a non-negative integer such as `--offset 20`.',
      { code: 'INVALID_OFFSET' }
    );
    process.exitCode = 1;
    return null;
  }

  return {
    fields,
    limit,
    offset,
  };
}

function colorizeInstallStateLabel(label) {
  if (!label) return '';
  return `${colors.cyan}[${label}]${colors.reset}`;
}

// ============ CONFIG FILE SUPPORT ============

function loadConfig() {
  try {
    if (fs.existsSync(CONFIG_FILE)) {
      return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
    }
  } catch (e) {
    warn(`Warning: Could not load config file: ${e.message}`);
  }
  return { defaultAgent: 'claude', autoUpdate: false };
}

function saveConfig(config) {
  try {
    fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
    return true;
  } catch (e) {
    error(`Failed to save config: ${e.message}`);
    return false;
  }
}

// ============ SKILL METADATA SUPPORT ============

function writeSkillMeta(skillPath, meta) {
  return writeInstalledMeta(skillPath, meta);
}

function readSkillMeta(skillPath) {
  return readInstalledMeta(skillPath);
}

// ============ SECURITY VALIDATION ============

const AGENT_INPUT_HINT = 'Remove path traversal (`../`), percent-encoded segments, fragments/query params, and control characters from the input.';
const AGENT_IDENTIFIER_FIELDS = new Set([
  'source',
  'name',
  'skill',
  'skillFilter',
  'collection',
  'removeFromCollection',
  'collectionRemove',
  'workArea',
  'area',
  'category',
  'trust',
  'ref',
  'id',
]);
const AGENT_FREEFORM_FIELDS = new Set([
  'why',
  'whyHere',
  'notes',
  'description',
  'branch',
  'tags',
  'labels',
  'title',
]);
const PROMPT_INJECTION_PATTERNS = [
  /<\/?system>/i,
  /\bignore\s+(?:all\s+)?previous\b/i,
  /\byou are now\b/i,
];
const BASE64ISH_LINE_PATTERN = /^[A-Za-z0-9+/]{80,}={0,2}$/;

function validateAgentInput(value, fieldName, options = {}) {
  if (value === null || value === undefined) return true;
  if (typeof value !== 'string') return true;

  const stringValue = String(value);

  if (/[\x00-\x1f\x7f]/.test(stringValue)) {
    throw new Error(`Invalid ${fieldName}: control characters are not allowed.`);
  }

  if (options.rejectPercentEncoding && /%(?:2e|2f|5c|00|23|3f)/i.test(stringValue)) {
    throw new Error(`Invalid ${fieldName}: percent-encoded path or query segments are not allowed.`);
  }

  if (options.rejectTraversal && /(?:^|[\\/])\.\.(?:[\\/]|$)/.test(stringValue)) {
    throw new Error(`Invalid ${fieldName}: path traversal is not allowed.`);
  }

  if (!options.allowQuery && /[?#]/.test(stringValue)) {
    throw new Error(`Invalid ${fieldName}: embedded query parameters or fragments are not allowed.`);
  }

  return true;
}

function validateAgentValue(value, fieldName, mode = 'text') {
  const options = mode === 'identifier'
    ? { allowQuery: false, rejectTraversal: true, rejectPercentEncoding: true }
    : { allowQuery: true, rejectTraversal: false, rejectPercentEncoding: false };

  if (Array.isArray(value)) {
    value.forEach((item, index) => validateAgentValue(item, `${fieldName}[${index}]`, mode));
    return true;
  }

  return validateAgentInput(value, fieldName, options);
}

function validateAgentPayloadValue(value, fieldName = 'payload', parentKey = '') {
  if (value === null || value === undefined) return;

  if (Array.isArray(value)) {
    value.forEach((item, index) => validateAgentPayloadValue(item, `${fieldName}[${index}]`, parentKey));
    return;
  }

  if (typeof value === 'string') {
    const mode = AGENT_IDENTIFIER_FIELDS.has(parentKey) || parentKey === 'workAreas' || parentKey === 'collections' || parentKey === 'skills'
      ? 'identifier'
      : 'text';
    validateAgentValue(value, fieldName, mode);
    return;
  }

  if (typeof value === 'object') {
    for (const [key, nestedValue] of Object.entries(value)) {
      validateAgentPayloadValue(nestedValue, fieldName === 'payload' ? key : `${fieldName}.${key}`, key);
    }
  }
}

function sandboxOutputPath(target, allowedRoot) {
  const resolved = path.resolve(target);
  const root = path.resolve(allowedRoot);
  if (!resolved.startsWith(root + path.sep) && resolved !== root) {
    throw new Error(`Output path "${target}" escapes the allowed root "${allowedRoot}".`);
  }
  return resolved;
}

function sanitizeSkillContent(content) {
  const source = String(content == null ? '' : content);
  const lines = source.split(/\r?\n/);
  let sanitized = false;
  const kept = lines.filter((line) => {
    const trimmed = line.trim();
    if (!trimmed) return true;
    if (PROMPT_INJECTION_PATTERNS.some((pattern) => pattern.test(line))) {
      sanitized = true;
      return false;
    }
    if (BASE64ISH_LINE_PATTERN.test(trimmed)) {
      sanitized = true;
      return false;
    }
    return true;
  });

  let safeContent = kept.join('\n');
  if (sanitized) {
    safeContent = safeContent.replace(/\n{3,}/g, '\n\n').trim();
    if (!safeContent) {
      safeContent = '[sanitized suspicious content removed]';
    }
  }

  return {
    content: sanitized ? safeContent : source,
    sanitized,
  };
}

function validateParsedAgentInputs(command, parsed, payload = null) {
  const canonical = resolveCommandAlias(command || parsed.command || '');
  const sourceLikeCommands = new Set(['install', 'add', 'catalog', 'vendor']);
  const nameLikeCommands = new Set(['info', 'show', 'preview', 'uninstall', 'remove', 'rm', 'sync', 'update', 'upgrade', 'curate']);
  const freeformParamCommands = new Set(['search', 'help', 'describe']);

  validateAgentValue(parsed.fields, 'fields', 'text');
  validateAgentValue(parsed.collection, 'collection', 'identifier');
  validateAgentValue(parsed.collectionRemove, 'removeFromCollection', 'identifier');
  validateAgentValue(parsed.workArea, 'workArea', 'identifier');
  validateAgentValue(parsed.category, 'category', 'identifier');
  validateAgentValue(parsed.trust, 'trust', 'identifier');
  validateAgentValue(parsed.lastVerified, 'lastVerified', 'text');
  validateAgentValue(parsed.branch, 'branch', 'text');
  validateAgentValue(parsed.tags, 'tags', 'text');
  validateAgentValue(parsed.labels, 'labels', 'text');
  validateAgentValue(parsed.notes, 'notes', 'text');
  validateAgentValue(parsed.why, 'why', 'text');
  validateAgentValue(parsed.description, 'description', 'text');
  validateAgentValue(parsed.skillFilters, 'skill', 'identifier');

  if (sourceLikeCommands.has(canonical)) {
    validateAgentValue(parsed.param, canonical === 'install' ? 'source' : 'source', 'identifier');
  } else if (nameLikeCommands.has(canonical)) {
    validateAgentValue(parsed.param, 'name', 'identifier');
  } else if (canonical === 'init-library') {
    validateAgentValue(parsed.param, 'name', 'identifier');
  } else if (freeformParamCommands.has(canonical)) {
    validateAgentValue(parsed.param, canonical === 'search' ? 'query' : 'command', 'text');
  }

  if (payload) {
    validateAgentPayloadValue(payload);
  }

  return true;
}

function validateSkillName(name) {
  if (!name || typeof name !== 'string') {
    throw new Error('Skill name is required');
  }

  // Check for path traversal attacks
  if (name.includes('..') || name.includes('/') || name.includes('\\')) {
    throw new Error(`Invalid skill name: "${name}" contains path characters`);
  }

  // Check for valid characters (lowercase, numbers, hyphens)
  if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {
    throw new Error(`Invalid skill name: "${name}" must be lowercase alphanumeric with hyphens`);
  }

  // Check length
  if (name.length > 64) {
    throw new Error(`Skill name too long: ${name.length} > 64 characters`);
  }

  return true;
}

function isSafePath(basePath, targetPath) {
  const normalizedBase = path.normalize(path.resolve(basePath));
  const normalizedTarget = path.normalize(path.resolve(targetPath));
  return normalizedTarget.startsWith(normalizedBase + path.sep)
    || normalizedTarget === normalizedBase;
}

function safeTempCleanup(dir) {
  try {
    const normalizedDir = path.normalize(path.resolve(dir));
    const normalizedTmp = path.normalize(path.resolve(os.tmpdir()));
    if (!normalizedDir.startsWith(normalizedTmp + path.sep)) {
      throw new Error('Attempted to clean up directory outside of temp directory');
    }
    fs.rmSync(dir, { recursive: true, force: true });
  } catch (cleanupErr) {
    // Swallow cleanup errors so they don't obscure the real error
  }
}

function validateGitHubSkillPath(skillPath) {
  if (!skillPath) return [];

  const segments = String(skillPath).split('/').filter(Boolean);
  if (segments.length === 0) {
    throw new Error('Invalid GitHub skill path');
  }

  segments.forEach((segment) => {
    if (segment === '.' || segment === '..') {
      throw new Error(`Invalid GitHub skill path segment: "${segment}"`);
    }
    if (!/^[a-zA-Z0-9._-]+$/.test(segment)) {
      throw new Error(`Invalid GitHub skill path segment: "${segment}" contains invalid characters`);
    }
  });

  return segments;
}

function parseSkillMarkdown(raw) {
  return parseSkillMarkdownFile(raw);
}

function readSkillDirectory(skillDir) {
  const skillMdPath = path.join(skillDir, 'SKILL.md');
  if (!fs.existsSync(skillMdPath)) {
    return null;
  }

  const raw = fs.readFileSync(skillMdPath, 'utf8');
  const parsed = parseSkillMarkdown(raw);
  if (!parsed) {
    return null;
  }

  return {
    skillMdPath,
    ...parsed,
  };
}

// ============ ERROR-SAFE JSON LOADING ============

function loadSkillsJson() {
  try {
    return loadCatalogData(getActiveLibraryContext());
  } catch (e) {
    throw new Error(`Failed to load skills.json: ${e.message}`);
  }
}

function getCollections(data) {
  return Array.isArray(data.collections) ? data.collections : [];
}

function getCollection(data, collectionId) {
  if (!collectionId) return null;
  return getCollections(data).find(collection => collection.id === collectionId);
}

function resolveCollection(data, collectionId) {
  if (!collectionId) {
    return {
      collection: null,
      message: null,
      unknown: false,
      retired: false
    };
  }

  const exact = getCollection(data, collectionId);
  if (exact) {
    return {
      collection: exact,
      message: null,
      unknown: false,
      retired: false
    };
  }

  const alias = LEGACY_COLLECTION_ALIASES[collectionId];
  if (!alias) {
    return {
      collection: null,
      message: `Unknown collection "${collectionId}"`,
      unknown: true,
      retired: false
    };
  }

  if (!alias.targetId) {
    return {
      collection: null,
      message: alias.message,
      unknown: false,
      retired: true
    };
  }

  const mapped = getCollection(data, alias.targetId);
  if (!mapped) {
    return {
      collection: null,
      message: `Collection "${collectionId}" now maps to "${alias.targetId}", but that collection is missing from skills.json.`,
      unknown: true,
      retired: false
    };
  }

  return {
    collection: mapped,
    message: alias.message,
    unknown: false,
    retired: false
  };
}

function uniquePaths(paths) {
  return [...new Set((paths || []).filter(Boolean))];
}

function getCollectionsForSkill(data, skillName) {
  return getCollections(data).filter(collection =>
    Array.isArray(collection.skills) && collection.skills.includes(skillName)
  );
}

function getCollectionBadgeText(data, skill, limit = 2) {
  const collections = getCollectionsForSkill(data, skill.name).slice(0, limit);
  if (collections.length === 0) return null;
  return collections.map(collection => collection.title).join(', ');
}

function getCollectionStartHere(collection, limit = 3) {
  return (collection?.skills || []).slice(0, limit);
}

function validateRemoteWorkspaceCatalog(data) {
  const errors = [];
  const names = new Set();

  for (const skill of data.skills || []) {
    if (!skill || !skill.name) continue;
    if (names.has(skill.name)) {
      errors.push(`Duplicate skill name: ${skill.name}`);
      break;
    }
    names.add(skill.name);
  }

  const dependencyGraph = buildDependencyGraph(data);
  errors.push(...dependencyGraph.errors);

  return errors;
}

function getSearchMatchScore(skill, query) {
  const q = query.toLowerCase();
  let score = 0;

  if (skill.name.toLowerCase() === q) score += 5000;
  else if (skill.name.toLowerCase().startsWith(q)) score += 3000;
  else if (skill.name.toLowerCase().includes(q)) score += 1800;

  if ((skill.workArea || '').toLowerCase() === q) score += 1200;
  if ((skill.branch || '').toLowerCase() === q) score += 1200;
  if ((skill.category || '').toLowerCase() === q) score += 1000;
  if ((skill.description || '').toLowerCase().includes(q)) score += 500;
  if ((skill.tags || []).some(tag => tag.toLowerCase() === q)) score += 900;
  else if ((skill.tags || []).some(tag => tag.toLowerCase().includes(q))) score += 300;

  return score;
}

function sortSkillsForSearch(data, skills, query) {
  return [...skills].sort((left, right) => {
    const scoreDiff = getSearchMatchScore(right, query) - getSearchMatchScore(left, query);
    if (scoreDiff !== 0) return scoreDiff;
    return compareSkillsByCurationData(data, left, right);
  });
}

function getWorkAreas(data) {
  return Array.isArray(data.workAreas) ? data.workAreas : [];
}

function formatWorkAreaTitle(workArea) {
  if (!workArea || typeof workArea !== 'string') return 'Other';
  if (workArea === 'docs') return 'Docs';
  return workArea
    .split('-')
    .map(token => token.charAt(0).toUpperCase() + token.slice(1))
    .join(' ');
}

function formatCount(count, singular, plural = `${singular}s`) {
  return `${count} ${count === 1 ? singular : plural}`;
}

function getWorkAreaMeta(data, workAreaId) {
  return getWorkAreas(data).find(area => area.id === workAreaId) || null;
}

function getSkillWorkArea(skill) {
  if (skill && typeof skill.workArea === 'string' && skill.workArea.trim()) {
    return skill.workArea;
  }
  return null;
}

function getSkillBranch(skill) {
  if (skill && typeof skill.branch === 'string' && skill.branch.trim()) {
    return skill.branch;
  }
  return null;
}

function getOrigin(skill) {
  if (skill && typeof skill.origin === 'string' && skill.origin.trim()) {
    return skill.origin;
  }
  return skill.source === 'MoizIbnYousaf/Ai-Agent-Skills' ? 'authored' : 'curated';
}

function getTrust(skill) {
  if (skill && typeof skill.trust === 'string' && skill.trust.trim()) {
    return skill.trust;
  }
  if (skill.verified) return 'verified';
  if (skill.featured) return 'reviewed';
  return 'listed';
}

function getSyncMode(skill) {
  if (skill && typeof skill.syncMode === 'string' && skill.syncMode.trim()) {
    return skill.syncMode;
  }
  const origin = getOrigin(skill);
  if (origin === 'authored' || origin === 'adapted') return origin;
  return 'snapshot';
}

function getTier(skill) {
  if (skill && (skill.tier === 'house' || skill.tier === 'upstream')) {
    return skill.tier;
  }
  return skill && skill.vendored === false ? 'upstream' : 'house';
}

function getDistribution(skill) {
  if (skill && (skill.distribution === 'bundled' || skill.distribution === 'live')) {
    return skill.distribution;
  }
  return getTier(skill) === 'house' ? 'bundled' : 'live';
}

function getTierBadge(skill) {
  if (getTier(skill) === 'house') {
    return `${colors.green}[house copy]${colors.reset}`;
  }
  return `${colors.magenta}[cataloged upstream]${colors.reset}`;
}

function getTierLine(skill) {
  if (getTier(skill) === 'house') {
    return 'House copy · bundled in this library';
  }
  return `Cataloged upstream · install pulls live from ${skill.installSource || skill.source}`;
}

function getSkillMeta(skill, includeCategory = true) {
  const parts = [];
  const workArea = getSkillWorkArea(skill);
  const branch = getSkillBranch(skill);
  if (workArea && branch) {
    parts.push(`${formatWorkAreaTitle(workArea)} / ${branch}`);
  } else if (workArea) {
    parts.push(formatWorkAreaTitle(workArea));
  } else if (includeCategory && skill.category) {
    parts.push(skill.category);
  }
  parts.push(getOrigin(skill));
  if (skill.source) parts.push(skill.source);
  return parts.join(' · ');
}

function filterSkillsByCollection(data, skills, collectionId) {
  if (!collectionId) {
    return { collection: null, skills, message: null, unknown: false, retired: false };
  }

  const resolution = resolveCollection(data, collectionId);
  if (!resolution.collection) {
    return {
      collection: null,
      skills: null,
      message: resolution.message,
      unknown: resolution.unknown,
      retired: resolution.retired
    };
  }

  const order = new Map(resolution.collection.skills.map((name, index) => [name, index]));
  const filtered = skills
    .filter(skill => order.has(skill.name))
    .sort((a, b) => order.get(a.name) - order.get(b.name));

  return {
    collection: resolution.collection,
    skills: filtered,
    message: resolution.message,
    unknown: false,
    retired: false
  };
}

function printCollectionSuggestions(data) {
  const collections = getCollections(data);
  if (collections.length === 0) return;

  log(`\n${colors.dim}Available collections:${colors.reset}`);
  collections.forEach(collection => {
    log(`  ${colors.cyan}${collection.id}${colors.reset} - ${collection.title}`);
  });
}

function getAvailableSkills() {
  const skills = [];
  const skillsDir = getActiveSkillsDir();

  // Vendored skills (local folders)
  if (fs.existsSync(skillsDir)) {
    try {
      skills.push(...fs.readdirSync(skillsDir).filter(name => {
        const skillPath = path.join(skillsDir, name);
        return fs.statSync(skillPath).isDirectory() &&
               fs.existsSync(path.join(skillPath, 'SKILL.md'));
      }));
    } catch (e) {
      error(`Failed to read skills directory: ${e.message}`);
    }
  }

  // Non-vendored cataloged skills (from skills.json)
  try {
    const data = loadSkillsJson();
    for (const skill of data.skills) {
      if (skill.vendored === false && !skills.includes(skill.name)) {
        skills.push(skill.name);
      }
    }
  } catch {}

  return skills;
}

// ============ ARGUMENT PARSING ============

function parseArgs(args) {
  const config = loadConfig();
  const validAgents = Object.keys(AGENT_PATHS);
  const validLegacyAgents = Object.keys(LEGACY_AGENTS);

  const result = {
    command: null,
    param: null,
    format: null,
    json: false,
    scope: null,          // v3: 'global', 'project', or null (default)
    agents: [],           // Legacy: array of agents
    allAgents: false,
    explicitAgent: false,
    installed: false,
    all: false,
    dryRun: false,
    noDeps: false,
    tags: null,
    labels: null,
    notes: null,
    why: null,
    branch: null,
    trust: null,
    description: null,
    lastVerified: null,
    featured: null,
    clearVerified: false,
    remove: false,
    category: null,
    workArea: null,
    workAreas: null,
    collection: null,
    collectionRemove: null,
    fields: null,
    limit: null,
    offset: null,
    skillFilters: [],     // v3: --skill flag values
    listMode: false,      // v3: --list flag
    yes: false,           // v3: --yes flag (non-interactive)
    importMode: false,
    autoClassify: false,
  };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];

    // v3 scope flags
    if (arg === '-p' || arg === '--project') {
      result.scope = 'project';
    }
    else if (arg === '-g' || arg === '--global') {
      result.scope = 'global';
    }
    else if (arg === '--format') {
      result.format = args[i + 1] || null;
      i++;
    }
    else if (arg === '--json') {
      result.json = true;
    }
    // v3 --skill filter
    else if (arg === '--skill') {
      const value = args[i + 1];
      if (value) {
        result.skillFilters.push(value);
        i++;
      }
    }
    // v3 --list flag
    else if (arg === '--list') {
      result.listMode = true;
    }
    // v3 --yes flag
    else if (arg === '--yes' || arg === '-y') {
      result.yes = true;
    }
    // --agents claude,cursor,codex (multiple agents)
    else if (arg === '--agents') {
      result.explicitAgent = true;
      const value = args[i + 1] || '';
      value.split(',').forEach(a => {
        const agent = a.trim();
        if (validAgents.includes(agent) && !result.agents.includes(agent)) {
          result.agents.push(agent);
        }
      });
      i++;
    }
    // --agent cursor (single agent, backward compatible)
    else if (arg === '--agent' || arg === '-a') {
      result.explicitAgent = true;
      let agentValue = args[i + 1] || 'claude';
      agentValue = agentValue.replace(/^-+/, '');
      if (validAgents.includes(agentValue) && !result.agents.includes(agentValue)) {
        result.agents.push(agentValue);
      }
      i++;
    }
    // --all-agents (install to all known agents)
    else if (arg === '--all-agents') {
      result.explicitAgent = true;
      result.allAgents = true;
    }
    else if (arg === '--installed' || arg === '-i') {
      result.installed = true;
    }
    else if (arg === '--all') {
      result.all = true;
    }
    else if (arg === '--dry-run' || arg === '-n') {
      result.dryRun = true;
    }
    else if (arg === '--no-deps') {
      result.noDeps = true;
    }
    else if (arg === '--tag' || arg === '--tags' || arg === '-t') {
      result.tags = args[i + 1];
      i++;
    }
    else if (arg === '--labels') {
      result.labels = args[i + 1];
      i++;
    }
    else if (arg === '--notes') {
      result.notes = args[i + 1];
      i++;
    }
    else if (arg === '--why') {
      result.why = args[i + 1];
      i++;
    }
    else if (arg === '--branch') {
      result.branch = args[i + 1];
      i++;
    }
    else if (arg === '--trust') {
      result.trust = args[i + 1];
      i++;
    }
    else if (arg === '--description') {
      result.description = args[i + 1];
      i++;
    }
    else if (arg === '--last-verified') {
      result.lastVerified = args[i + 1];
      i++;
    }
    else if (arg === '--feature') {
      result.featured = true;
    }
    else if (arg === '--unfeature') {
      result.featured = false;
    }
    else if (arg === '--verify') {
      result.trust = 'verified';
    }
    else if (arg === '--unverify' || arg === '--clear-verified') {
      result.clearVerified = true;
    }
    else if (arg === '--remove') {
      result.remove = true;
    }
    else if (arg === '--category' || arg === '-c') {
      result.category = args[i + 1];
      i++;
    }
    else if (arg === '--work-area' || arg === '--area') {
      result.workArea = args[i + 1];
      i++;
    }
    else if (arg === '--areas') {
      result.workAreas = args[i + 1] || null;
      i++;
    }
    else if (arg === '--collection') {
      result.collection = args[i + 1];
      i++;
    }
    else if (arg === '--remove-from-collection') {
      result.collectionRemove = args[i + 1];
      i++;
    }
    else if (arg === '--fields') {
      result.fields = args[i + 1] || null;
      i++;
    }
    else if (arg === '--limit') {
      const value = args[i + 1];
      result.limit = value == null ? NaN : Number.parseInt(value, 10);
      i++;
    }
    else if (arg === '--offset') {
      const value = args[i + 1];
      result.offset = value == null ? NaN : Number.parseInt(value, 10);
      i++;
    }
    else if (arg === '--import') {
      result.importMode = true;
    }
    else if (arg === '--auto-classify') {
      result.autoClassify = true;
    }
    else if (arg.startsWith('--')) {
      const potentialAgent = arg.replace(/^--/, '');
      if (validAgents.includes(potentialAgent)) {
        result.explicitAgent = true;
        if (!result.agents.includes(potentialAgent)) {
          result.agents.push(potentialAgent);
        }
      } else if (!result.command) {
        result.command = arg;
      }
    }
    else if (!result.command) {
      result.command = args[i];
    } else if (!result.param) {
      result.param = args[i];
    }
  }

  // Resolve final agents list
  if (result.allAgents) {
    result.agents = [...validAgents];
  } else if (result.agents.length === 0) {
    // Use config agents or default
    const configAgents = config.agents && config.agents.length > 0
      ? config.agents.filter(a => validAgents.includes(a))
      : [];
    result.agents = configAgents.length > 0 ? configAgents : ['claude'];
  }

  return result;
}

const JSON_INPUT_COMMANDS = new Set(['add', 'catalog', 'vendor', 'curate', 'init-library', 'uninstall']);
const INVALID_JSON_INPUT = Symbol('invalid-json-input');

async function readJsonStdin() {
  const chunks = [];
  for await (const chunk of process.stdin) {
    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
  }

  const raw = Buffer.concat(chunks).toString('utf8').trim();
  if (!raw) {
    throw new Error('Expected a JSON object on stdin when using --json.');
  }

  let payload;
  try {
    payload = JSON.parse(raw);
  } catch (error) {
    throw new Error(`Invalid JSON payload: ${error.message}`);
  }

  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
    throw new Error('JSON payload must be an object.');
  }

  return payload;
}

async function parseJsonInput(command, parsed) {
  const canonical = resolveCommandAlias(command || '');
  if (!parsed.json || !JSON_INPUT_COMMANDS.has(canonical)) {
    return null;
  }

  try {
    return await readJsonStdin();
  } catch (error) {
    emitActionableError(
      error.message,
      'Pipe a JSON object to stdin, for example: echo \'{"name":"frontend-design"}\' | npx ai-agent-skills add --json',
      { code: 'INVALID_JSON_INPUT' }
    );
    process.exitCode = 1;
    return INVALID_JSON_INPUT;
  }
}

function getPayloadValue(payload, ...keys) {
  if (!payload || typeof payload !== 'object') return undefined;
  for (const key of keys) {
    if (payload[key] !== undefined) {
      return payload[key];
    }
  }
  return undefined;
}

function mergeMutationOption(cliValue, payload, ...keys) {
  return cliValue !== null && cliValue !== undefined ? cliValue : getPayloadValue(payload, ...keys);
}

function mergeMutationNullableBoolean(cliValue, payload, ...keys) {
  if (cliValue !== null && cliValue !== undefined) {
    return cliValue;
  }
  const value = getPayloadValue(payload, ...keys);
  return value === undefined ? null : Boolean(value);
}

function mergeMutationBoolean(cliValue, payload, ...keys) {
  if (cliValue) return true;
  const value = getPayloadValue(payload, ...keys);
  return value === undefined ? false : Boolean(value);
}

function resolveMutationSource(param, payload, options = {}) {
  if (param) return param;
  const source = getPayloadValue(payload, 'source');
  if (source !== undefined) return source;
  return options.allowNameFallback ? (getPayloadValue(payload, 'name') || null) : null;
}

function buildWorkspaceMutationOptions(parsed, payload = {}) {
  return {
    list: mergeMutationBoolean(parsed.listMode, payload, 'list'),
    skillFilter: parsed.skillFilters.length > 0 ? parsed.skillFilters[0] : getPayloadValue(payload, 'skill', 'name'),
    area: mergeMutationOption(parsed.workArea, payload, 'workArea', 'area'),
    branch: mergeMutationOption(parsed.branch, payload, 'branch'),
    category: mergeMutationOption(parsed.category, payload, 'category'),
    tags: mergeMutationOption(parsed.tags, payload, 'tags'),
    labels: mergeMutationOption(parsed.labels, payload, 'labels'),
    notes: mergeMutationOption(parsed.notes, payload, 'notes'),
    trust: mergeMutationOption(parsed.trust, payload, 'trust'),
    whyHere: mergeMutationOption(parsed.why, payload, 'whyHere', 'why'),
    description: mergeMutationOption(parsed.description, payload, 'description'),
    collections: mergeMutationOption(parsed.collection, payload, 'collections', 'collection'),
    lastVerified: mergeMutationOption(parsed.lastVerified, payload, 'lastVerified'),
    featured: mergeMutationNullableBoolean(parsed.featured, payload, 'featured'),
    clearVerified: mergeMutationBoolean(parsed.clearVerified, payload, 'clearVerified'),
    remove: mergeMutationBoolean(parsed.remove, payload, 'remove'),
    ref: getArgValue(process.argv, '--ref') || getPayloadValue(payload, 'ref') || null,
    dryRun: mergeMutationBoolean(parsed.dryRun, payload, 'dryRun'),
  };
}

function buildCurateParsed(parsed, payload = {}) {
  return {
    ...parsed,
    workArea: mergeMutationOption(parsed.workArea, payload, 'workArea', 'area'),
    branch: mergeMutationOption(parsed.branch, payload, 'branch'),
    category: mergeMutationOption(parsed.category, payload, 'category'),
    tags: mergeMutationOption(parsed.tags, payload, 'tags'),
    labels: mergeMutationOption(parsed.labels, payload, 'labels'),
    notes: mergeMutationOption(parsed.notes, payload, 'notes'),
    trust: mergeMutationOption(parsed.trust, payload, 'trust'),
    why: mergeMutationOption(parsed.why, payload, 'whyHere', 'why'),
    description: mergeMutationOption(parsed.description, payload, 'description'),
    collection: mergeMutationOption(parsed.collection, payload, 'collections', 'collection'),
    collectionRemove: mergeMutationOption(parsed.collectionRemove, payload, 'removeFromCollection', 'collectionRemove'),
    featured: mergeMutationNullableBoolean(parsed.featured, payload, 'featured'),
    lastVerified: mergeMutationOption(parsed.lastVerified, payload, 'lastVerified'),
    clearVerified: mergeMutationBoolean(parsed.clearVerified, payload, 'clearVerified'),
    remove: mergeMutationBoolean(parsed.remove, payload, 'remove'),
    yes: mergeMutationBoolean(parsed.yes, payload, 'yes'),
    dryRun: mergeMutationBoolean(parsed.dryRun, payload, 'dryRun'),
  };
}

// v3: resolve install target path from scope/agent flags
function resolveInstallPath(parsed, options = {}) {
  // 1. Explicit legacy --agent override
  if (parsed.explicitAgent && parsed.agents.length > 0) {
    return uniquePaths(parsed.agents.map(a => AGENT_PATHS[a] || SCOPES.global));
  }
  // 2. --all installs to both scopes
  if (parsed.all) {
    return uniquePaths([SCOPES.global, SCOPES.project]);
  }
  // 3. Explicit scope flag
  if (parsed.scope === 'project') return [SCOPES.project];
  if (parsed.scope === 'global') return [SCOPES.global];
  // 4. Optional default agents for direct source shortcuts
  if (Array.isArray(options.defaultAgents) && options.defaultAgents.length > 0) {
    return uniquePaths(options.defaultAgents.map((agent) => AGENT_PATHS[agent] || SCOPES.global));
  }
  // 4. Default: global
  return [SCOPES.global];
}

function resolveManagedTargets(parsed) {
  if (parsed.explicitAgent && parsed.agents.length > 0) {
    return parsed.agents.map((agent) => ({
      label: agent,
      path: AGENT_PATHS[agent] || SCOPES.global,
    }));
  }

  if (parsed.scope === 'project') {
    return [{ label: 'project', path: SCOPES.project }];
  }

  if (parsed.scope === 'global') {
    return [{ label: 'global', path: SCOPES.global }];
  }

  return parsed.agents.map((agent) => ({
    label: agent,
    path: AGENT_PATHS[agent] || SCOPES.global,
  }));
}

// v3: resolve scope label for metadata
function resolveScopeLabel(targetPath) {
  if (targetPath === SCOPES.global) return 'global';
  if (targetPath === SCOPES.project) return 'project';
  return 'legacy';
}

function isKnownCommand(command) {
  return COMMAND_ALIAS_MAP.has(command);
}

function isImplicitSourceCommand(command) {
  const parsed = parseSource(command);
  return parsed.type !== 'catalog';
}

// ============ SAFE FILE OPERATIONS ============

function copyDir(src, dest, currentSize = { total: 0 }, rootSrc = null) {
  // Track root source to prevent path escape attacks
  if (rootSrc === null) rootSrc = src;

  try {
    if (fs.existsSync(dest)) {
      fs.rmSync(dest, { recursive: true });
    }
    fs.mkdirSync(dest, { recursive: true });

    const entries = fs.readdirSync(src, { withFileTypes: true });

    // Files/folders to skip during copy
    const skipList = ['.git', '.github', 'node_modules', '.DS_Store'];

    for (const entry of entries) {
      // Skip unnecessary files/folders
      if (skipList.includes(entry.name)) continue;

      // Skip symlinks to prevent path escape attacks
      if (entry.isSymbolicLink()) {
        warn(`Skipping symlink: ${entry.name}`);
        continue;
      }

      const srcPath = path.join(src, entry.name);
      const destPath = path.join(dest, entry.name);

      // Verify resolved path stays within source directory (prevent path traversal)
      const resolvedSrc = fs.realpathSync(srcPath);
      if (!resolvedSrc.startsWith(fs.realpathSync(rootSrc))) {
        warn(`Skipping file outside source directory: ${entry.name}`);
        continue;
      }

      if (entry.isDirectory()) {
        copyDir(srcPath, destPath, currentSize, rootSrc);
      } else if (entry.isFile()) {
        const stat = fs.statSync(srcPath);
        currentSize.total += stat.size;

        if (currentSize.total > MAX_SKILL_SIZE) {
          throw new Error(`Skill exceeds maximum size of ${MAX_SKILL_SIZE / 1024 / 1024}MB`);
        }

        fs.copyFileSync(srcPath, destPath);
      }
      // Skip any other special file types (sockets, devices, etc.)
    }
  } catch (e) {
    // Clean up partial install on failure
    if (fs.existsSync(dest)) {
      try { fs.rmSync(dest, { recursive: true }); } catch {}
    }
    throw e;
  }
}

function getDirectorySize(dir) {
  let size = 0;
  try {
    const entries = fs.readdirSync(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);
      if (entry.isDirectory()) {
        size += getDirectorySize(fullPath);
      } else {
        size += fs.statSync(fullPath).size;
      }
    }
  } catch {}
  return size;
}

// ============ CORE COMMANDS ============

function buildHouseSkillInstallMeta(skillName, destDir, {
  sourceContext = getActiveLibraryContext(),
  skill = null,
  sourceParsed = null,
  libraryRepo = null,
} = {}) {
  const relativePath = getCatalogSkillRelativePath(skill || { name: skillName });

  if (!sourceParsed) {
    return buildCatalogInstallMeta(skillName, destDir, sourceContext);
  }

  if (sourceParsed.type === 'local') {
    return {
      sourceType: 'local',
      source: 'local',
      path: resolveCatalogSkillSourcePath(skillName, { sourceContext, skill }),
      skillName,
      scope: resolveScopeLabel(destDir),
      ...(libraryRepo ? { libraryRepo } : {}),
    };
  }

  return {
    sourceType: sourceParsed.type,
    source: sourceParsed.type,
    url: sourceParsed.type === 'git' ? sanitizeGitUrl(sourceParsed.url) : sourceParsed.url,
    repo: buildRepoId(sourceParsed),
    ref: sourceParsed.ref || null,
    subpath: relativePath,
    installSource: buildInstallSourceRef(sourceParsed, relativePath),
    skillName,
    scope: resolveScopeLabel(destDir),
    ...(libraryRepo ? { libraryRepo } : {}),
  };
}

function installSkill(skillName, agent = 'claude', dryRun = false, targetPath = null, options = {}) {
  try {
    validateSkillName(skillName);
  } catch (e) {
    error(e.message);
    return false;
  }

  const sourceContext = options.sourceContext || getActiveLibraryContext();
  const skill = options.skill || null;
  const sourcePath = resolveCatalogSkillSourcePath(skillName, { sourceContext, skill });

  if (!fs.existsSync(sourcePath)) {
    // Check if this is a non-vendored cataloged skill
    try {
      const data = loadCatalogData(sourceContext);
      const cataloged = data.skills.find(s => s.name === skillName) || null;
      if (cataloged && shouldTreatCatalogSkillAsHouse(cataloged, sourceContext)) {
        emitActionableError(
          `House copy files for "${skillName}" are missing in ${sourceContext.rootDir}`,
          'Check the `path` in skills.json and commit the vendored files to the shared library.',
          { code: 'HOUSE_PATH' }
        );
        return false;
      }
      if (cataloged && cataloged.tier === 'upstream') {
        const installSource = cataloged.installSource || cataloged.source;
        if (installSource) {
          info(`"${skillName}" is a cataloged upstream skill. Installing live from ${installSource}...`);
          const parsed = parseSource(installSource);
          const installPaths = targetPath ? [targetPath] : [AGENT_PATHS[agent] || SCOPES.global];
          return installFromSource(installSource, parsed, installPaths, [skillName], false, true, dryRun, {
            additionalInstallMeta: options.additionalInstallMeta || null,
            allowWorkspaceCatalog: options.allowWorkspaceCatalog !== false,
          });
        }
      }
    } catch {}

    error(`Skill "${skillName}" not found.`);

    // Suggest similar skills
    const available = getAvailableSkills();
    const similar = available.filter(s =>
      s.includes(skillName) || skillName.includes(s) ||
      levenshteinDistance(s, skillName) <= 3
    ).slice(0, 3);

    if (similar.length > 0) {
      log(`\n${colors.dim}Did you mean: ${similar.join(', ')}?${colors.reset}`);
    }
    return false;
  }

  const destDir = targetPath || AGENT_PATHS[agent] || SCOPES.global;
  const destPath = path.join(destDir, skillName);
  sandboxOutputPath(destPath, destDir);
  const skillSize = getDirectorySize(sourcePath);

  if (dryRun) {
    const scopeLabel = resolveScopeLabel(destDir);
    log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
    info(`Would install: ${skillName}`);
    info(`Scope: ${scopeLabel}`);
    info(`Source: ${sourcePath}`);
    info(`Destination: ${destPath}`);
    info(`Size: ${(skillSize / 1024).toFixed(1)} KB`);

    if (fs.existsSync(destPath)) {
      warn(`Note: Would overwrite existing installation`);
    }
    return true;
  }

  try {
    if (!fs.existsSync(destDir)) {
      fs.mkdirSync(destDir, { recursive: true });
    }

    copyDir(sourcePath, destPath);

    // Write metadata for update tracking
    writeSkillMeta(destPath, options.metadata || buildHouseSkillInstallMeta(skillName, destDir, {
      sourceContext,
      skill,
      sourceParsed: options.sourceParsed || null,
      libraryRepo: options.libraryRepo || null,
    }));

    const scopeLabel = resolveScopeLabel(destDir);
    success(`\nInstalled: ${skillName}`);
    info(`Scope: ${scopeLabel}`);
    info(`Location: ${destPath}`);
    info(`Size: ${(skillSize / 1024).toFixed(1)} KB`);

    log('');
    if (agent && options.includeAgentInstructions !== false) {
      showAgentInstructions(agent, skillName, destPath);
    }

    return true;
  } catch (e) {
    error(`Failed to install skill: ${e.message}`);
    return false;
  }
}

// v3: install a catalog skill to a scope path directly (for TUI scope chooser)
function installSkillToScope(skillName, scopePath, scopeLabel, dryRun = false, options = {}) {
  try { validateSkillName(skillName); } catch (e) { error(e.message); return false; }

  const sourceContext = options.sourceContext || getActiveLibraryContext();
  const skill = options.skill || null;
  const sourcePath = resolveCatalogSkillSourcePath(skillName, { sourceContext, skill });
  if (!fs.existsSync(sourcePath)) {
    try {
      const data = loadCatalogData(sourceContext);
      const cataloged = data.skills.find((skill) => skill.name === skillName && skill.tier === 'upstream');
      if (cataloged && cataloged.installSource) {
        const parsed = parseSource(cataloged.installSource);
        return installFromSource(cataloged.installSource, parsed, [scopePath], [skillName], false, true, dryRun, {
          additionalInstallMeta: options.additionalInstallMeta || null,
          allowWorkspaceCatalog: options.allowWorkspaceCatalog !== false,
        });
      }
    } catch {}

    error(`Skill "${skillName}" not found.`);
    const available = getAvailableSkills();
    const similar = available.filter(s => s.includes(skillName) || skillName.includes(s) || levenshteinDistance(s, skillName) <= 3).slice(0, 3);
    if (similar.length > 0) log(`\n${colors.dim}Did you mean: ${similar.join(', ')}?${colors.reset}`);
    return false;
  }

  const destPath = path.join(scopePath, skillName);
  sandboxOutputPath(destPath, scopePath);
  const skillSize = getDirectorySize(sourcePath);

  if (dryRun) {
    log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
    info(`Would install: ${skillName}`);
    info(`Scope: ${scopeLabel}`);
    info(`Destination: ${destPath}`);
    info(`Size: ${(skillSize / 1024).toFixed(1)} KB`);
    return true;
  }

  try {
    if (!fs.existsSync(scopePath)) fs.mkdirSync(scopePath, { recursive: true });
    copyDir(sourcePath, destPath);
    writeSkillMeta(destPath, {
      ...(options.metadata || buildHouseSkillInstallMeta(skillName, scopePath, {
        sourceContext,
        skill,
        sourceParsed: options.sourceParsed || null,
        libraryRepo: options.libraryRepo || null,
      })),
      scope: scopeLabel,
    });
    success(`\nInstalled: ${skillName}`);
    info(`Scope: ${scopeLabel}`);
    info(`Location: ${destPath}`);
    info(`Size: ${(skillSize / 1024).toFixed(1)} KB`);
    if (scopeLabel === 'global') {
      log(`${colors.dim}The skill is now available in your default global Agent Skills location.\nCompatible agents can pick it up from there.${colors.reset}`);
    } else {
      log(`${colors.dim}The skill is installed in .agents/skills/ for this project.\nAny Agent Skills-compatible agent in this repo can read it.${colors.reset}`);
    }
    return true;
  } catch (e) {
    error(`Failed to install skill: ${e.message}`);
    return false;
  }
}

function getCollectionSkillsInOrder(data, collection) {
  const orderedSkills = [];
  for (const skillName of collection.skills || []) {
    const skill = findSkillByName(data, skillName);
    if (skill) {
      orderedSkills.push(skill);
    }
  }
  return orderedSkills;
}

function buildCollectionInstallOperations(skills, { sourceContext = getActiveLibraryContext() } = {}) {
  const operations = [];

  for (const skill of skills) {
    if (!skill) continue;

    if (shouldTreatCatalogSkillAsHouse(skill, sourceContext)) {
      operations.push({
        type: 'skill',
        skills: [skill],
      });
      continue;
    }

    const upstreamSourceRef = getCatalogSkillSourceRef(skill, { sourceContext });
    const previous = operations[operations.length - 1];
    if (previous && previous.type === 'upstream' && previous.source === upstreamSourceRef) {
      previous.skills.push(skill);
      continue;
    }

    operations.push({
      type: 'upstream',
      source: upstreamSourceRef,
      skills: [skill],
    });
  }

  return operations;
}

function printCatalogInstallPlan(plan, installPaths, {
  dryRun = false,
  title = 'Install plan',
  summaryLine = null,
  sourceContext = getActiveLibraryContext(),
  sourceParsed = null,
  parseable = false,
} = {}) {
  const requestedCount = plan.requested.size;
  const targetList = installPaths.join(', ');
  const usesSparseCheckout = plan.skills.some((skill) => !shouldTreatCatalogSkillAsHouse(skill, sourceContext) && (skill.installSource || skill.source) !== skill.source);

  if (parseable) {
    if (isJsonOutput()) {
      emitJsonRecord('install', {
        kind: 'plan',
        requested: requestedCount,
        resolved: plan.skills.length,
        targets: installPaths,
      });

      for (const skill of plan.skills) {
        emitJsonRecord('install', {
          kind: 'install',
          skill: {
            name: skill.name,
            tier: shouldTreatCatalogSkillAsHouse(skill, sourceContext) ? 'house' : 'upstream',
            source: getCatalogSkillSourceRef(skill, { sourceContext, sourceParsed }),
          },
        });
      }
      return;
    }

    emitMachineLine('PLAN', [
      `requested=${requestedCount}`,
      `resolved=${plan.skills.length}`,
      `targets=${targetList}`,
    ]);

    for (const skill of plan.skills) {
      emitMachineLine('INSTALL', [
        skill.name,
        shouldTreatCatalogSkillAsHouse(skill, sourceContext) ? 'house' : 'upstream',
        getCatalogSkillSourceRef(skill, { sourceContext, sourceParsed }),
      ]);
    }
    return;
  }

  if (dryRun) {
    log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
  } else {
    log(`\n${colors.bold}${title}${colors.reset}`);
  }

  if (summaryLine) {
    info(summaryLine);
  }
  info(`Targets: ${targetList}`);
  info(`Requested: ${requestedCount} skill${requestedCount === 1 ? '' : 's'}`);
  info(`Resolved: ${plan.skills.length} skill${plan.skills.length === 1 ? '' : 's'}`);

  if (plan.skills.length > plan.requested.size) {
    info(`Dependency order: ${plan.orderedNames.join(' -> ')}`);
  }
  if (usesSparseCheckout) {
    info('Clone mode: sparse checkout');
  }

  for (const skill of plan.skills) {
    const sourceLabel = shouldTreatCatalogSkillAsHouse(skill, sourceContext)
      ? `bundled house copy from ${getCatalogSkillSourceRef(skill, { sourceContext, sourceParsed })}`
      : `live from ${skill.installSource || skill.source}`;
    const dependencyLabel = plan.requested.has(skill.name)
      ? ''
      : ` ${colors.dim}(dependency)${colors.reset}`;
    log(`  ${colors.green}${skill.name}${colors.reset}${dependencyLabel} ${colors.dim}(${sourceLabel})${colors.reset}`);
  }
}

async function installCatalogPlan(plan, installPaths, {
  dryRun = false,
  title = 'Installing skills',
  summaryLine = null,
  successLine = null,
  sourceContext = getActiveLibraryContext(),
  sourceParsed = null,
  libraryRepo = null,
  parseable = false,
} = {}) {
  if (dryRun) {
    printCatalogInstallPlan(plan, installPaths, {
      dryRun: true,
      title,
      summaryLine,
      sourceContext,
      sourceParsed,
      parseable,
    });
    return true;
  }

  printCatalogInstallPlan(plan, installPaths, {
    dryRun: false,
    title,
    summaryLine,
    sourceContext,
    sourceParsed,
  });

  const operations = buildCollectionInstallOperations(plan.skills, { sourceContext });
  let completed = 0;
  let failed = 0;

  for (const operation of operations) {
    if (operation.type === 'upstream') {
      const upstreamSource = operation.source;
      const success = await installFromSource(
        upstreamSource,
        parseSource(upstreamSource),
        installPaths,
        operation.skills.map((skill) => skill.name),
        false,
        true,
        false,
        {
          additionalInstallMeta: libraryRepo ? { libraryRepo } : null,
          allowWorkspaceCatalog: false,
        }
      );

      if (success) completed += operation.skills.length;
      else failed += operation.skills.length;
      continue;
    }

    for (const skill of operation.skills) {
      let skillSucceeded = true;
      for (const targetPath of installPaths) {
        if (!installSkill(skill.name, null, false, targetPath, {
          sourceContext,
          sourceParsed,
          skill,
          libraryRepo,
          includeAgentInstructions: false,
          metadata: buildHouseSkillInstallMeta(skill.name, targetPath, {
            sourceContext,
            sourceParsed,
            skill,
            libraryRepo,
          }),
        })) {
          skillSucceeded = false;
        }
      }

      if (skillSucceeded) completed += 1;
      else failed += 1;
    }
  }

  if (completed > 0) {
    success(`\n${successLine || `Finished: ${completed} skill${completed === 1 ? '' : 's'} completed`}`);
  }
  if (failed > 0) {
    emitActionableError(
      `${failed} skill${failed === 1 ? '' : 's'} failed during install`,
      'Run the source again with --dry-run or --list to inspect the install plan and failing source.',
      { code: 'INSTALL', machine: parseable }
    );
    process.exitCode = 1;
  }

  return completed > 0;
}

async function installCatalogSkillFromLibrary(skillName, installPaths, dryRun = false) {
  const data = loadSkillsJson();
  const skill = findSkillByName(data, skillName);
  if (!skill) {
    for (const targetPath of installPaths) {
      installSkill(skillName, null, dryRun, targetPath);
    }
    return false;
  }

  const plan = getCatalogInstallPlan(data, [skillName], false);
  return installCatalogPlan(plan, installPaths, {
    dryRun,
    title: `Installing ${skillName}`,
    summaryLine: `Would install: ${skillName}`,
  });
}

async function installCollection(collectionId, parsed, installPaths) {
  const data = loadSkillsJson();
  const resolution = resolveCollection(data, collectionId);

  if (!resolution.collection) {
    warn(resolution.message);
    if (resolution.unknown) {
      printCollectionSuggestions(data);
    }
    return false;
  }

  if (resolution.message) {
    info(resolution.message);
  }

  const orderedSkills = getCollectionSkillsInOrder(data, resolution.collection);
  if (orderedSkills.length === 0) {
    warn(`Collection "${resolution.collection.id}" has no installable skills.`);
    return false;
  }

  const plan = getCatalogInstallPlan(
    data,
    orderedSkills.map((skill) => skill.name),
    parsed.noDeps,
  );

  return installCatalogPlan(plan, installPaths, {
    dryRun: parsed.dryRun,
    title: 'Installing Collection',
    summaryLine: `Would install collection: ${resolution.collection.title} [${resolution.collection.id}]`,
    successLine: `Collection install finished: ${plan.skills.length} skill${plan.skills.length === 1 ? '' : 's'} completed`,
  });
}

function showAgentInstructions(agent, skillName, destPath) {
  const instructions = {
    claude: `The skill is now available in Claude Code.\nJust mention "${skillName}" in your prompt and Claude will use it.`,
    cursor: `The skill is installed in your project's .cursor/skills/ folder.\nCursor will automatically detect and use it.`,
    amp: `The skill is now available in Amp.`,
    codex: `The skill is now available in Codex.`,
    vscode: `The skill is installed in your project's .github/skills/ folder.`,
    copilot: `The skill is installed in your project's .github/skills/ folder.`,
    project: `The skill is installed in .skills/ in your current directory.\nThis makes it portable across all compatible agents.`,
    letta: `The skill is now available in Letta.`,
    goose: `The skill is now available in Goose.`,
    opencode: `The skill is now available in OpenCode.`,
    kilocode: `The skill is now available in Kilo Code.\nKiloCode will automatically detect and use it.`,
    gemini: `The skill is now available in Gemini CLI.\nMake sure Agent Skills is enabled in your Gemini CLI settings.`
  };

  log(`${colors.dim}${instructions[agent] || `The skill is ready to use with ${agent}.`}${colors.reset}`);
}

function uninstallSkill(skillName, agent = 'claude', dryRun = false) {
  const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
  return uninstallSkillFromPath(skillName, destDir, agent, dryRun);
}

function uninstallSkillFromPath(skillName, destDir, targetLabel = 'global', dryRun = false) {
  try {
    validateSkillName(skillName);
  } catch (e) {
    error(e.message);
    return false;
  }

  const skillPath = path.join(destDir, skillName);

  if (!fs.existsSync(skillPath)) {
    error(`Skill "${skillName}" is not installed in ${targetLabel}.`);
    log(`\nInstalled skills in ${targetLabel}:`);
    listInstalledSkillsInPath(destDir, targetLabel);
    return false;
  }

  if (dryRun) {
    log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
    info(`Would uninstall: ${skillName}`);
    info(`Target: ${targetLabel}`);
    info(`Path: ${skillPath}`);
    return true;
  }

  try {
    fs.rmSync(skillPath, { recursive: true });
    success(`\nUninstalled: ${skillName}`);
    info(`Target: ${targetLabel}`);
    info(`Removed from: ${skillPath}`);
    return true;
  } catch (e) {
    error(`Failed to uninstall skill: ${e.message}`);
    return false;
  }
}

function getInstalledSkills(agent = 'claude') {
  const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
  return getInstalledSkillsInPath(destDir);
}

function getInstalledSkillsInPath(destDir) {
  return listInstalledSkillNamesInDir(destDir);
}

function listInstalledSkills(agent = 'claude') {
  const installed = getInstalledSkills(agent);
  const destDir = AGENT_PATHS[agent] || AGENT_PATHS.claude;
  return listInstalledSkillsInPath(destDir, agent, installed);
}

function listInstalledSkillsInPath(destDir, label = 'global', installed = null) {
  let resolvedInstalled = Array.isArray(installed) ? installed : null;
  if (!resolvedInstalled) {
    if (label === 'global' || label === 'project') {
      const installStateIndex = buildInstallStateIndex();
      resolvedInstalled = getInstalledSkillNames(installStateIndex, label);
    } else {
      resolvedInstalled = getInstalledSkillsInPath(destDir);
    }
  }

  if (resolvedInstalled.length === 0) {
    warn(`No skills installed in ${label}`);
    info(`Location: ${destDir}`);
    return;
  }

  log(`\n${colors.bold}Installed Skills${colors.reset} (${resolvedInstalled.length} in ${label})\n`);
  log(`${colors.dim}Location: ${destDir}${colors.reset}\n`);

  resolvedInstalled.forEach(name => {
    log(`  ${colors.green}${name}${colors.reset}`);
  });

  if (label === 'project') {
    log(`\n${colors.dim}Sync:      npx ai-agent-skills sync <name> --project${colors.reset}`);
    log(`${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --project${colors.reset}`);
    return;
  }

  if (label === 'global') {
    log(`\n${colors.dim}Sync:      npx ai-agent-skills sync <name> --global${colors.reset}`);
    log(`${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --global${colors.reset}`);
    return;
  }

  log(`\n${colors.dim}Sync:      npx ai-agent-skills sync <name> --agent ${label}${colors.reset}`);
  log(`${colors.dim}Uninstall: npx ai-agent-skills uninstall <name> --agent ${label}${colors.reset}`);
}

function runDoctor(agentsToCheck = Object.keys(AGENT_PATHS)) {
  const checks = [];
  const context = getActiveLibraryContext();

  try {
    const data = loadCatalogData(context);
    const vendoredSkills = (data.skills || []).filter(s => s.tier === 'house');
    const catalogedSkills = (data.skills || []).filter(s => s.tier === 'upstream');
    const missingSkills = vendoredSkills.filter((skill) => {
      const skillPath = path.join(resolveCatalogSkillSourcePath(skill.name, { sourceContext: context, skill }), 'SKILL.md');
      return !fs.existsSync(skillPath);
    });

    const vendoredCount = vendoredSkills.length;
    const catalogedCount = catalogedSkills.length;
    checks.push({
      name: context.mode === 'workspace' ? 'Workspace library' : 'Bundled library',
      pass: missingSkills.length === 0,
      detail: missingSkills.length === 0
        ? `${vendoredCount} vendored + ${catalogedCount} cataloged upstream across ${getCollections(data).length} collections`
        : `Missing SKILL.md for ${missingSkills.map((skill) => skill.name).join(', ')}`,
    });
  } catch (e) {
    checks.push({
      name: context.mode === 'workspace' ? 'Workspace library' : 'Bundled library',
      pass: false,
      detail: `Failed to load skills.json: ${e.message}`,
    });
  }

  if (!fs.existsSync(CONFIG_FILE)) {
    checks.push({
      name: 'Config file',
      pass: true,
      detail: `Not created yet; defaults will be used at ${CONFIG_FILE}`,
    });
  } else {
    try {
      JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
      checks.push({
        name: 'Config file',
        pass: true,
        detail: `Readable at ${CONFIG_FILE}`,
      });
    } catch (e) {
      checks.push({
        name: 'Config file',
        pass: false,
        detail: `Invalid JSON at ${CONFIG_FILE}: ${e.message}`,
      });
    }
  }

  agentsToCheck.forEach((agent) => {
    const targetPath = AGENT_PATHS[agent] || AGENT_PATHS.claude;
    const access = getPathAccessStatus(targetPath);
    const installedCount = getInstalledSkills(agent).length;
    const brokenCount = getBrokenInstalledEntries(agent).length;
    const detailParts = [access.detail, `${installedCount} installed`];
    if (brokenCount > 0) {
      detailParts.push(`${brokenCount} broken entries`);
    }

    checks.push({
      name: `${agent} target`,
      pass: access.pass && brokenCount === 0,
      detail: detailParts.join(' · '),
    });
  });

  let passed = 0;
  let failed = 0;
  checks.forEach((check) => {
    if (check.pass) passed++;
    else failed++;
  });

  if (isJsonOutput()) {
    setJsonResultData({
      checks,
      summary: {
        passed,
        failed,
      },
    });
    if (failed > 0) {
      process.exitCode = 1;
    }
    return;
  }

  log(`\n${colors.bold}AI Agent Skills Doctor${colors.reset}`);
  log(`${colors.dim}Checking the library, config, and install targets.${colors.reset}\n`);
  checks.forEach((check) => {
    const badge = check.pass
      ? `${colors.green}${colors.bold}PASS${colors.reset}`
      : `${colors.red}${colors.bold}FAIL${colors.reset}`;
    log(`  [${badge}] ${check.name}`);
    log(`      ${colors.dim}${check.detail}${colors.reset}`);
    log('');
    if (check.pass) passed++;
    else failed++;
  });

  log(`${colors.bold}Summary:${colors.reset} ${colors.green}${passed} passed${colors.reset}, ${failed > 0 ? `${colors.red}${failed} failed${colors.reset}` : `${colors.dim}0 failed${colors.reset}`}\n`);

  if (failed > 0) {
    process.exitCode = 1;
  }
}

function runValidate(targetPath) {
  const result = validateSkillDirectory(targetPath);
  const label = targetPath ? expandPath(targetPath) : process.cwd();

  if (isJsonOutput()) {
    setJsonResultData({
      target: label,
      ok: result.ok,
      skillDir: result.skillDir,
      summary: result.summary,
      errors: result.errors,
      warnings: result.warnings,
    });
    if (!result.ok) {
      process.exitCode = 1;
    }
    return;
  }

  log(`\n${colors.bold}Validate Skill${colors.reset}`);
  log(`${colors.dim}${label}${colors.reset}\n`);

  if (!result.summary) {
    result.errors.forEach((message) => log(`  ${colors.red}${colors.bold}ERROR${colors.reset} ${message}`));
    log('');
    process.exitCode = 1;
    return;
  }

  result.errors.forEach((message) => log(`  ${colors.red}${colors.bold}ERROR${colors.reset} ${message}`));
  result.warnings.forEach((message) => log(`  ${colors.yellow}${colors.bold}WARN${colors.reset}  ${message}`));

  if (result.errors.length === 0 && result.warnings.length === 0) {
    log(`  ${colors.green}${colors.bold}PASS${colors.reset} Skill is valid`);
  }

  log('');
  log(`  ${colors.bold}Name:${colors.reset} ${result.summary.name || 'n/a'}`);
  log(`  ${colors.bold}Description:${colors.reset} ${result.summary.description || 'n/a'}`);
  log(`  ${colors.bold}Size:${colors.reset} ${(result.summary.totalSize / 1024).toFixed(1)}KB`);
  log(`  ${colors.bold}Path:${colors.reset} ${result.skillDir}`);
  log('');

  if (!result.ok) {
    process.exitCode = 1;
  }
}

// Update from the library catalog
function updateFromRegistry(skillName, targetLabel, destPath, dryRun, meta = null) {
  const catalogContext = getCatalogContextFromMeta(meta);
  if (!catalogContext) {
    error('The workspace library for this installed skill is unavailable.');
    log(`${colors.dim}Run this command from inside the workspace or reinstall the skill.${colors.reset}`);
    return false;
  }
  const data = loadCatalogData(catalogContext);
  const skill = findSkillByName(data, skillName);
  const sourcePath = skill
    ? resolveCatalogSkillSourcePath(skillName, { sourceContext: catalogContext, skill })
    : path.join(catalogContext.skillsDir, skillName);

  if (!fs.existsSync(sourcePath)) {
    error(`Skill "${skillName}" not found in ${catalogContext.mode === 'workspace' ? 'workspace' : 'bundled'} library.`);
    return false;
  }

  if (dryRun) {
    log(`\n${colors.bold}Dry Run${colors.reset} (no changes made)\n`);
    info(`Would update: ${skillName} (from catalog)`);
    info(`Target: ${targetLabel}`);
    info(`Path: ${destPath}`);
    return true;
  }

  try {
    fs.rmSync(destPath, { recursive: true });
    copyDir(sourcePath, destPath);

    // Write metadata
    writeSkillMeta(destPath, {
      ...(meta || {}),
      ...buildCatalogInstallMeta(skillName, path.dirname(destPath), catalogContext),
      scope: resolveScopeLabel(path.dirname(destPath)),
    });

    success(`\nUpdated: ${skillName}`);
    info(`Target: ${targetLabel}`);
    info(`Location: ${destPath}`);
    return true;
  } catch (e) {
    error(`Failed to update skill: ${e.message}`);
    return false;
  }
}

function updateFromRemoteSource(meta, skillName, targetLabel, destPath, dryRun) {
  const sourceType = meta.sourceType || meta.source;
  const scopeLabel = meta.scope || resolveScopeLabel(path.dirname(destPath));

  let parsed;
  let sourceLabel;

  if (sourceType === 'github') {
    if (!meta.repo || typeof meta.repo !== 'string' || !meta.repo.includes('/')) {
      error(`Invalid repository in metadata: ${meta.repo}`);
      error('Try reinstalling the skill from GitHub.');
      return false;
    }

    const [owner, repo] = meta.repo.split('/');
    parsed = {
      type: 'github',
      url: `https://github.com/${meta.repo}`,
      owner,
      repo,
      ref: meta.ref || null,
      subpath: meta.subpath || null,
    };
    sourceLabel = `github:${meta.repo}`;
  } else if (sourceType === 'git') {
    if (!meta.url || typeof meta.url !== 'string') {
      error('Invalid git URL in metadata. Try reinstalling the skill.');
      return false;
    }

    try {
      validateGitUrl(meta.url);
    } catch (e) {
      error(`Invalid git URL in metadata: ${e.message}. Try reinstalling the skill.`);
      return false;
    }

    parsed = {
      type: 'git',
      url: meta.url,
      ref: meta.ref || null,
      su
Download .txt
gitextract_zrmsvrzw/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── config.yml
│   │   └── skill-request.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       └── validate.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── CURATION.md
├── FOR_YOUR_AGENT.md
├── LICENSE
├── README.md
├── WORK_AREAS.md
├── atlas.html
├── cli.js
├── curator.html
├── lib/
│   ├── catalog-data.cjs
│   ├── catalog-mutations.cjs
│   ├── catalog-paths.cjs
│   ├── dependency-graph.cjs
│   ├── frontmatter.cjs
│   ├── install-metadata.cjs
│   ├── install-state.cjs
│   ├── library-context.cjs
│   ├── paths.cjs
│   ├── render-docs.cjs
│   ├── source.cjs
│   └── workspace-import.cjs
├── package.json
├── scripts/
│   ├── render-docs.js
│   ├── test-live.js
│   ├── validate.js
│   └── vendor.js
├── skills/
│   ├── ask-questions-if-underspecified/
│   │   └── SKILL.md
│   ├── audit-library-health/
│   │   └── SKILL.md
│   ├── backend-development/
│   │   └── SKILL.md
│   ├── best-practices/
│   │   ├── SKILL.md
│   │   ├── agents/
│   │   │   ├── best-practices-referencer.md
│   │   │   ├── codebase-context-builder.md
│   │   │   └── task-intent-analyzer.md
│   │   └── references/
│   │       ├── anti-patterns.md
│   │       ├── before-after-examples.md
│   │       ├── best-practices-guide.md
│   │       ├── common-workflows.md
│   │       └── prompt-patterns.md
│   ├── browse-and-evaluate/
│   │   └── SKILL.md
│   ├── build-workspace-docs/
│   │   └── SKILL.md
│   ├── changelog-generator/
│   │   └── SKILL.md
│   ├── code-documentation/
│   │   └── SKILL.md
│   ├── content-research-writer/
│   │   └── SKILL.md
│   ├── curate-a-team-library/
│   │   └── SKILL.md
│   ├── database-design/
│   │   └── SKILL.md
│   ├── install-from-remote-library/
│   │   └── SKILL.md
│   ├── llm-application-dev/
│   │   └── SKILL.md
│   ├── migrate-skills-between-libraries/
│   │   └── SKILL.md
│   ├── review-a-skill/
│   │   └── SKILL.md
│   ├── share-a-library/
│   │   └── SKILL.md
│   └── update-installed-skills/
│       └── SKILL.md
├── skills.json
├── test.js
└── tui/
    ├── catalog.cjs
    └── index.mjs
Download .txt
SYMBOL INDEX (575 symbols across 18 files)

FILE: cli.js
  constant LEGACY_COLLECTION_ALIASES (line 103) | const LEGACY_COLLECTION_ALIASES = {
  constant SWIFT_SHORTCUT (line 134) | const SWIFT_SHORTCUT = 'swift';
  constant MKTG_SHORTCUT (line 135) | const MKTG_SHORTCUT = 'mktg';
  constant UNIVERSAL_DEFAULT_AGENTS (line 136) | const UNIVERSAL_DEFAULT_AGENTS = ['claude', 'codex'];
  constant FORMAT_ENUM (line 137) | const FORMAT_ENUM = ['text', 'json'];
  constant WORK_AREA_ENUM (line 138) | const WORK_AREA_ENUM = ['frontend', 'backend', 'mobile', 'workflow', 'ag...
  constant CATEGORY_ENUM (line 139) | const CATEGORY_ENUM = ['development', 'document', 'creative', 'business'...
  constant TRUST_ENUM (line 140) | const TRUST_ENUM = ['listed', 'verified'];
  constant TIER_ENUM (line 141) | const TIER_ENUM = ['house', 'upstream'];
  constant DISTRIBUTION_ENUM (line 142) | const DISTRIBUTION_ENUM = ['bundled', 'live'];
  constant ORIGIN_ENUM (line 143) | const ORIGIN_ENUM = ['authored', 'curated', 'adapted'];
  constant SYNC_MODE_ENUM (line 144) | const SYNC_MODE_ENUM = ['snapshot', 'live', 'authored', 'adapted'];
  constant FLAG_DEFINITIONS (line 146) | const FLAG_DEFINITIONS = {
  constant COMMAND_REGISTRY (line 185) | const COMMAND_REGISTRY = {
  constant COMMAND_ALIAS_MAP (line 344) | const COMMAND_ALIAS_MAP = Object.entries(COMMAND_REGISTRY).reduce((map, ...
  function resolveCommandAlias (line 352) | function resolveCommandAlias(command) {
  function getCommandDefinition (line 356) | function getCommandDefinition(command) {
  function getFlagSchema (line 361) | function getFlagSchema(flagName) {
  function stringSchema (line 370) | function stringSchema(description = null, extra = {}) {
  function booleanSchema (line 378) | function booleanSchema(description = null, extra = {}) {
  function integerSchema (line 386) | function integerSchema(description = null, extra = {}) {
  function enumSchema (line 394) | function enumSchema(values, description = null, extra = {}) {
  function arraySchema (line 403) | function arraySchema(items, description = null, extra = {}) {
  function objectSchema (line 412) | function objectSchema(properties, required = [], description = null, ext...
  function oneOfSchema (line 423) | function oneOfSchema(variants, description = null, extra = {}) {
  function nullableSchema (line 431) | function nullableSchema(schema) {
  function buildEnvelopeSchema (line 438) | function buildEnvelopeSchema(commandName, dataSchema, description = null) {
  function buildNdjsonSchema (line 457) | function buildNdjsonSchema(commandName, summarySchema, itemSchema, descr...
  constant STRING_OR_STRING_ARRAY_SCHEMA (line 483) | const STRING_OR_STRING_ARRAY_SCHEMA = oneOfSchema([
  constant COLLECTION_INPUT_SCHEMA (line 488) | const COLLECTION_INPUT_SCHEMA = oneOfSchema([
  constant WORK_AREA_INPUT_SCHEMA (line 493) | const WORK_AREA_INPUT_SCHEMA = oneOfSchema([
  constant STARTER_COLLECTION_INPUT_SCHEMA (line 502) | const STARTER_COLLECTION_INPUT_SCHEMA = oneOfSchema([
  constant SERIALIZED_SKILL_SCHEMA (line 512) | const SERIALIZED_SKILL_SCHEMA = objectSchema({
  function buildMutationStdinSchema (line 532) | function buildMutationStdinSchema(commandName) {
  function buildCommandInputSchema (line 603) | function buildCommandInputSchema(commandName) {
  constant IMPORT_RESULT_SCHEMA (line 610) | const IMPORT_RESULT_SCHEMA = objectSchema({
  function buildCommandOutputSchema (line 653) | function buildCommandOutputSchema(commandName) {
  function getCommandSchema (line 976) | function getCommandSchema(command) {
  function buildHelpSchema (line 994) | function buildHelpSchema(command = null) {
  function emitSchemaHelp (line 1025) | function emitSchemaHelp(command = null) {
  constant ANSI_PATTERN (line 1030) | const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
  constant OUTPUT_STATE (line 1031) | let OUTPUT_STATE = {
  function stripAnsi (line 1041) | function stripAnsi(value) {
  function resolveOutputFormat (line 1045) | function resolveOutputFormat(parsed = {}) {
  function resetOutputState (line 1053) | function resetOutputState(format = 'text', command = null, explicitForma...
  function isJsonOutput (line 1065) | function isJsonOutput() {
  function captureMessage (line 1069) | function captureMessage(level, value) {
  function log (line 1077) | function log(msg) {
  function success (line 1085) | function success(msg) {
  function info (line 1093) | function info(msg) {
  function warn (line 1101) | function warn(msg) {
  function error (line 1109) | function error(msg) {
  function setJsonResultData (line 1117) | function setJsonResultData(data) {
  function emitJsonEnvelope (line 1121) | function emitJsonEnvelope(command, data = null, errors = null, options =...
  function emitJsonRecord (line 1132) | function emitJsonRecord(command, data = null, errors = null, options = {...
  function finalizeJsonOutput (line 1143) | function finalizeJsonOutput() {
  function isMachineReadableOutput (line 1148) | function isMachineReadableOutput() {
  function sanitizeMachineField (line 1152) | function sanitizeMachineField(value) {
  function emitMachineLine (line 1159) | function emitMachineLine(kind, fields = []) {
  function emitActionableError (line 1163) | function emitActionableError(message, hint = '', options = {}) {
  function emitDryRunResult (line 1187) | function emitDryRunResult(command, actions = [], extra = {}) {
  constant ACTIVE_LIBRARY_CONTEXT (line 1205) | let ACTIVE_LIBRARY_CONTEXT = getBundledLibraryContext();
  function setActiveLibraryContext (line 1207) | function setActiveLibraryContext(context) {
  function getActiveLibraryContext (line 1212) | function getActiveLibraryContext() {
  function getActiveSkillsDir (line 1216) | function getActiveSkillsDir() {
  function getLibraryDisplayName (line 1220) | function getLibraryDisplayName(context = getActiveLibraryContext()) {
  function getLibraryModeHint (line 1228) | function getLibraryModeHint(context = getActiveLibraryContext()) {
  function requireWorkspaceContext (line 1235) | function requireWorkspaceContext(actionLabel = 'This command') {
  function slugifyLibraryName (line 1246) | function slugifyLibraryName(name) {
  function isInsideDirectory (line 1254) | function isInsideDirectory(targetPath, candidatePath) {
  function isMaintainerRepoContext (line 1259) | function isMaintainerRepoContext(context) {
  function requireEditableLibraryContext (line 1265) | function requireEditableLibraryContext(actionLabel = 'This command') {
  function getCatalogContextFromMeta (line 1281) | function getCatalogContextFromMeta(meta) {
  function buildCatalogInstallMeta (line 1306) | function buildCatalogInstallMeta(skillName, targetDir, context = getActi...
  function getBundledCatalogData (line 1320) | function getBundledCatalogData() {
  function getBundledCatalogSkill (line 1324) | function getBundledCatalogSkill(skillName) {
  function inferInstallSourceFromCatalogSkill (line 1329) | function inferInstallSourceFromCatalogSkill(skill) {
  function buildImportedCatalogEntryFromBundledSkill (line 1342) | function buildImportedCatalogEntryFromBundledSkill(skill, fields) {
  function getCatalogInstallOrder (line 1373) | function getCatalogInstallOrder(data, requestedSkillNames, noDeps = fals...
  function getCatalogInstallPlan (line 1381) | function getCatalogInstallPlan(data, requestedSkillNames, noDeps = false) {
  function getInstallStateText (line 1395) | function getInstallStateText(skillName, index = buildInstallStateIndex()) {
  function serializeSkillForJson (line 1399) | function serializeSkillForJson(data, skill, installStateIndex = null) {
  constant DEFAULT_LIST_JSON_FIELDS (line 1423) | const DEFAULT_LIST_JSON_FIELDS = ['name', 'tier', 'workArea', 'descripti...
  constant DEFAULT_COLLECTIONS_JSON_FIELDS (line 1424) | const DEFAULT_COLLECTIONS_JSON_FIELDS = ['id', 'title', 'description', '...
  constant DEFAULT_PREVIEW_JSON_FIELDS (line 1425) | const DEFAULT_PREVIEW_JSON_FIELDS = ['name', 'sourceType', 'content', 's...
  constant DEFAULT_INSTALL_LIST_JSON_FIELDS (line 1426) | const DEFAULT_INSTALL_LIST_JSON_FIELDS = ['name', 'description'];
  constant DEFAULT_REMOTE_INSTALL_LIST_JSON_FIELDS (line 1427) | const DEFAULT_REMOTE_INSTALL_LIST_JSON_FIELDS = ['name', 'tier', 'workAr...
  function parseFieldMask (line 1429) | function parseFieldMask(value, fallback = null) {
  function selectObjectFields (line 1438) | function selectObjectFields(record, fields) {
  function paginateItems (line 1448) | function paginateItems(items, limit = null, offset = null) {
  function applyTopLevelFieldMask (line 1464) | function applyTopLevelFieldMask(payload, fields, fallback = null) {
  function resolveReadJsonOptions (line 1476) | function resolveReadJsonOptions(parsed, commandName) {
  function colorizeInstallStateLabel (line 1508) | function colorizeInstallStateLabel(label) {
  function loadConfig (line 1515) | function loadConfig() {
  function saveConfig (line 1526) | function saveConfig(config) {
  function writeSkillMeta (line 1538) | function writeSkillMeta(skillPath, meta) {
  function readSkillMeta (line 1542) | function readSkillMeta(skillPath) {
  constant AGENT_INPUT_HINT (line 1548) | const AGENT_INPUT_HINT = 'Remove path traversal (`../`), percent-encoded...
  constant AGENT_IDENTIFIER_FIELDS (line 1549) | const AGENT_IDENTIFIER_FIELDS = new Set([
  constant AGENT_FREEFORM_FIELDS (line 1564) | const AGENT_FREEFORM_FIELDS = new Set([
  constant PROMPT_INJECTION_PATTERNS (line 1574) | const PROMPT_INJECTION_PATTERNS = [
  constant BASE64ISH_LINE_PATTERN (line 1579) | const BASE64ISH_LINE_PATTERN = /^[A-Za-z0-9+/]{80,}={0,2}$/;
  function validateAgentInput (line 1581) | function validateAgentInput(value, fieldName, options = {}) {
  function validateAgentValue (line 1606) | function validateAgentValue(value, fieldName, mode = 'text') {
  function validateAgentPayloadValue (line 1619) | function validateAgentPayloadValue(value, fieldName = 'payload', parentK...
  function sandboxOutputPath (line 1642) | function sandboxOutputPath(target, allowedRoot) {
  function sanitizeSkillContent (line 1651) | function sanitizeSkillContent(content) {
  function validateParsedAgentInputs (line 1683) | function validateParsedAgentInputs(command, parsed, payload = null) {
  function validateSkillName (line 1721) | function validateSkillName(name) {
  function isSafePath (line 1744) | function isSafePath(basePath, targetPath) {
  function safeTempCleanup (line 1751) | function safeTempCleanup(dir) {
  function validateGitHubSkillPath (line 1764) | function validateGitHubSkillPath(skillPath) {
  function parseSkillMarkdown (line 1784) | function parseSkillMarkdown(raw) {
  function readSkillDirectory (line 1788) | function readSkillDirectory(skillDir) {
  function loadSkillsJson (line 1808) | function loadSkillsJson() {
  function getCollections (line 1816) | function getCollections(data) {
  function getCollection (line 1820) | function getCollection(data, collectionId) {
  function resolveCollection (line 1825) | function resolveCollection(data, collectionId) {
  function uniquePaths (line 1882) | function uniquePaths(paths) {
  function getCollectionsForSkill (line 1886) | function getCollectionsForSkill(data, skillName) {
  function getCollectionBadgeText (line 1892) | function getCollectionBadgeText(data, skill, limit = 2) {
  function getCollectionStartHere (line 1898) | function getCollectionStartHere(collection, limit = 3) {
  function validateRemoteWorkspaceCatalog (line 1902) | function validateRemoteWorkspaceCatalog(data) {
  function getSearchMatchScore (line 1921) | function getSearchMatchScore(skill, query) {
  function sortSkillsForSearch (line 1939) | function sortSkillsForSearch(data, skills, query) {
  function getWorkAreas (line 1947) | function getWorkAreas(data) {
  function formatWorkAreaTitle (line 1951) | function formatWorkAreaTitle(workArea) {
  function formatCount (line 1960) | function formatCount(count, singular, plural = `${singular}s`) {
  function getWorkAreaMeta (line 1964) | function getWorkAreaMeta(data, workAreaId) {
  function getSkillWorkArea (line 1968) | function getSkillWorkArea(skill) {
  function getSkillBranch (line 1975) | function getSkillBranch(skill) {
  function getOrigin (line 1982) | function getOrigin(skill) {
  function getTrust (line 1989) | function getTrust(skill) {
  function getSyncMode (line 1998) | function getSyncMode(skill) {
  function getTier (line 2007) | function getTier(skill) {
  function getDistribution (line 2014) | function getDistribution(skill) {
  function getTierBadge (line 2021) | function getTierBadge(skill) {
  function getTierLine (line 2028) | function getTierLine(skill) {
  function getSkillMeta (line 2035) | function getSkillMeta(skill, includeCategory = true) {
  function filterSkillsByCollection (line 2051) | function filterSkillsByCollection(data, skills, collectionId) {
  function printCollectionSuggestions (line 2081) | function printCollectionSuggestions(data) {
  function getAvailableSkills (line 2091) | function getAvailableSkills() {
  function parseArgs (line 2123) | function parseArgs(args) {
  constant JSON_INPUT_COMMANDS (line 2358) | const JSON_INPUT_COMMANDS = new Set(['add', 'catalog', 'vendor', 'curate...
  constant INVALID_JSON_INPUT (line 2359) | const INVALID_JSON_INPUT = Symbol('invalid-json-input');
  function readJsonStdin (line 2361) | async function readJsonStdin() {
  function parseJsonInput (line 2386) | async function parseJsonInput(command, parsed) {
  function getPayloadValue (line 2405) | function getPayloadValue(payload, ...keys) {
  function mergeMutationOption (line 2415) | function mergeMutationOption(cliValue, payload, ...keys) {
  function mergeMutationNullableBoolean (line 2419) | function mergeMutationNullableBoolean(cliValue, payload, ...keys) {
  function mergeMutationBoolean (line 2427) | function mergeMutationBoolean(cliValue, payload, ...keys) {
  function resolveMutationSource (line 2433) | function resolveMutationSource(param, payload, options = {}) {
  function buildWorkspaceMutationOptions (line 2440) | function buildWorkspaceMutationOptions(parsed, payload = {}) {
  function buildCurateParsed (line 2463) | function buildCurateParsed(parsed, payload = {}) {
  function resolveInstallPath (line 2487) | function resolveInstallPath(parsed, options = {}) {
  function resolveManagedTargets (line 2507) | function resolveManagedTargets(parsed) {
  function resolveScopeLabel (line 2530) | function resolveScopeLabel(targetPath) {
  function isKnownCommand (line 2536) | function isKnownCommand(command) {
  function isImplicitSourceCommand (line 2540) | function isImplicitSourceCommand(command) {
  function copyDir (line 2547) | function copyDir(src, dest, currentSize = { total: 0 }, rootSrc = null) {
  function getDirectorySize (line 2605) | function getDirectorySize(dir) {
  function buildHouseSkillInstallMeta (line 2623) | function buildHouseSkillInstallMeta(skillName, destDir, {
  function installSkill (line 2660) | function installSkill(skillName, agent = 'claude', dryRun = false, targe...
  function installSkillToScope (line 2768) | function installSkillToScope(skillName, scopePath, scopeLabel, dryRun = ...
  function getCollectionSkillsInOrder (line 2835) | function getCollectionSkillsInOrder(data, collection) {
  function buildCollectionInstallOperations (line 2846) | function buildCollectionInstallOperations(skills, { sourceContext = getA...
  function printCatalogInstallPlan (line 2877) | function printCatalogInstallPlan(plan, installPaths, {
  function installCatalogPlan (line 2958) | async function installCatalogPlan(plan, installPaths, {
  function installCatalogSkillFromLibrary (line 3054) | async function installCatalogSkillFromLibrary(skillName, installPaths, d...
  function installCollection (line 3072) | async function installCollection(collectionId, parsed, installPaths) {
  function showAgentInstructions (line 3108) | function showAgentInstructions(agent, skillName, destPath) {
  function uninstallSkill (line 3127) | function uninstallSkill(skillName, agent = 'claude', dryRun = false) {
  function uninstallSkillFromPath (line 3132) | function uninstallSkillFromPath(skillName, destDir, targetLabel = 'globa...
  function getInstalledSkills (line 3169) | function getInstalledSkills(agent = 'claude') {
  function getInstalledSkillsInPath (line 3174) | function getInstalledSkillsInPath(destDir) {
  function listInstalledSkills (line 3178) | function listInstalledSkills(agent = 'claude') {
  function listInstalledSkillsInPath (line 3184) | function listInstalledSkillsInPath(destDir, label = 'global', installed ...
  function runDoctor (line 3224) | function runDoctor(agentsToCheck = Object.keys(AGENT_PATHS)) {
  function runValidate (line 3335) | function runValidate(targetPath) {
  function updateFromRegistry (line 3384) | function updateFromRegistry(skillName, targetLabel, destPath, dryRun, me...
  function updateFromRemoteSource (line 3431) | function updateFromRemoteSource(meta, skillName, targetLabel, destPath, ...
  function updateFromGitHub (line 3549) | function updateFromGitHub(meta, skillName, targetLabel, destPath, dryRun) {
  function updateFromGitUrl (line 3553) | function updateFromGitUrl(meta, skillName, targetLabel, destPath, dryRun) {
  function updateFromLocalPath (line 3558) | function updateFromLocalPath(meta, skillName, targetLabel, destPath, dry...
  function updateSkill (line 3604) | function updateSkill(skillName, agent = 'claude', dryRun = false) {
  function updateSkillInPath (line 3609) | function updateSkillInPath(skillName, destDir, targetLabel = 'global', d...
  function updateAllSkills (line 3648) | function updateAllSkills(agent = 'claude', dryRun = false) {
  function updateAllSkillsInPath (line 3653) | function updateAllSkillsInPath(destDir, targetLabel = 'global', dryRun =...
  function resolveCatalogSkillSelection (line 3679) | function resolveCatalogSkillSelection(category = null, tags = null, coll...
  function emitListJson (line 3714) | function emitListJson(category = null, tags = null, collectionId = null,...
  function emitSearchJson (line 3763) | function emitSearchJson(query, category = null, collectionId = null, wor...
  function emitInstalledSkillsJson (line 3827) | function emitInstalledSkillsJson(targets) {
  function listSkills (line 3855) | function listSkills(category = null, tags = null, collectionId = null, w...
  function searchSkills (line 3998) | function searchSkills(query, category = null, collectionId = null, workA...
  function showCollections (line 4085) | function showCollections(options = {}) {
  function getBundledSkillFilePath (line 4145) | function getBundledSkillFilePath(skillName, options = {}) {
  function showPreview (line 4168) | function showPreview(skillName, options = {}) {
  function isInteractiveTerminal (line 4246) | function isInteractiveTerminal() {
  function launchBrowser (line 4250) | async function launchBrowser({agent = null, scope = 'global'} = {}) {
  function runExternalInstallAction (line 4256) | function runExternalInstallAction(action) {
  function levenshteinDistance (line 4279) | function levenshteinDistance(a, b) {
  function sanitizeSubpath (line 4310) | function sanitizeSubpath(subpath) { return sanitizeSubpathLib(subpath); }
  function parseSource (line 4311) | function parseSource(source) { return parseSourceLib(source); }
  function isGitHubUrl (line 4313) | function isGitHubUrl(source) {
  function isGitUrl (line 4323) | function isGitUrl(source) { return isGitUrlLib(source); }
  function parseGitUrl (line 4324) | function parseGitUrl(source) { return parseGitUrlLib(source); }
  function getRepoNameFromUrl (line 4325) | function getRepoNameFromUrl(url) { return getRepoNameFromUrlLib(url); }
  function validateGitUrl (line 4326) | function validateGitUrl(url) { return validateGitUrlLib(url); }
  function sanitizeGitUrl (line 4327) | function sanitizeGitUrl(url) { return sanitizeGitUrlLib(url); }
  function isWindowsPath (line 4328) | function isWindowsPath(source) { return isWindowsPathLib(source); }
  function isLocalPath (line 4329) | function isLocalPath(source) { return isLocalPathLib(source); }
  function expandPath (line 4330) | function expandPath(p) { return expandPathLib(p); }
  function getArgValue (line 4332) | function getArgValue(argv, flag) {
  function createPromptInterface (line 4337) | function createPromptInterface() {
  function promptLine (line 4344) | function promptLine(rl, label, defaultValue = '') {
  function promptConfirm (line 4354) | function promptConfirm(label, defaultYes = true) {
  function promptForEditorialFields (line 4375) | async function promptForEditorialFields(initialFields, options = {}) {
  function formatReviewQueue (line 4440) | function formatReviewQueue(queue) {
  function buildCurateChanges (line 4470) | function buildCurateChanges(parsed) {
  function validateGitHubName (line 4491) | function validateGitHubName(name, type = 'name') {
  function findNearestExistingParent (line 4505) | function findNearestExistingParent(targetPath) {
  function getPathAccessStatus (line 4517) | function getPathAccessStatus(targetPath) {
  function getBrokenInstalledEntries (line 4546) | function getBrokenInstalledEntries(agent = 'claude') {
  function validateSkillDirectory (line 4565) | function validateSkillDirectory(skillTarget) {
  function discoverSkills (line 4657) | function discoverSkills(rootDir, repoRoot = rootDir) {
  function buildRepoId (line 4661) | function buildRepoId(parsed) {
  function buildInstallSourceRef (line 4668) | function buildInstallSourceRef(parsed, relativeDir = null) {
  function getLibraryRepoProvenance (line 4695) | function getLibraryRepoProvenance(parsed) {
  function getCatalogSkillSourceRef (line 4703) | function getCatalogSkillSourceRef(skill, { sourceContext = getActiveLibr...
  function buildSourceUrl (line 4713) | function buildSourceUrl(parsed, relativeDir = null) {
  function maybeRenameRootSkill (line 4727) | function maybeRenameRootSkill(discovered, parsed, rootDir, repoRoot) {
  function findDiscoveredSkill (line 4750) | function findDiscoveredSkill(discovered, filter) {
  function uniqueSkillFilters (line 4760) | function uniqueSkillFilters(filters = []) {
  function catalogSkills (line 4776) | async function catalogSkills(source, options = {}) {
  function vendorSkill (line 4917) | async function vendorSkill(source, options = {}) {
  function addBundledSkillToWorkspace (line 5068) | async function addBundledSkillToWorkspace(skillName, options = {}) {
  function addSkillToWorkspace (line 5147) | async function addSkillToWorkspace(source, options = {}) {
  function runCurateCommand (line 5165) | function runCurateCommand(skillName, parsed) {
  function classifyGitError (line 5276) | function classifyGitError(message) {
  function copySkillFiles (line 5281) | function copySkillFiles(srcDir, destDir, sandboxRoot) {
  function defaultImportClassification (line 5309) | function defaultImportClassification(workAreas) {
  function buildImportedSkillEntry (line 5318) | function buildImportedSkillEntry(candidate, workspaceData, options = {}) {
  function emitImportResult (line 5368) | function emitImportResult(result, options = {}) {
  function importWorkspaceSkills (line 5446) | function importWorkspaceSkills(importPath = null, options = {}) {
  function getSourceLabel (line 5632) | function getSourceLabel(parsed, fallbackSource = '') {
  function printRemoteWorkspaceList (line 5646) | function printRemoteWorkspaceList(sourceLabel, data, skills, options = {...
  function emitInstallSourceListJson (line 5701) | function emitInstallSourceListJson(sourceLabel, discovered, options = {}) {
  function installFromWorkspaceSource (line 5730) | async function installFromWorkspaceSource(source, parsed, prepared, inst...
  function installFromSource (line 5843) | async function installFromSource(source, parsed, installPaths, skillFilt...
  function installFromGitHub (line 6035) | async function installFromGitHub(source, agent = 'claude', dryRun = fals...
  function installFromGitUrl (line 6041) | async function installFromGitUrl(source, agent = 'claude', dryRun = fals...
  function installFromLocalPath (line 6047) | function installFromLocalPath(source, agent = 'claude', dryRun = false) {
  function showHelp (line 6055) | function showHelp() {
  function showInfo (line 6160) | function showInfo(skillName, options = {}) {
  function showConfig (line 6315) | function showConfig() {
  function setConfig (line 6340) | function setConfig(key, value) {
  function initSkill (line 6385) | function initSkill(name, options = {}) {
  function buildWorkspaceReadmeTemplate (line 6453) | function buildWorkspaceReadmeTemplate(libraryName) {
  constant DEFAULT_WORKSPACE_WORK_AREAS (line 6490) | const DEFAULT_WORKSPACE_WORK_AREAS = [
  function normalizeWorkspaceWorkAreas (line 6518) | function normalizeWorkspaceWorkAreas(workAreas) {
  function normalizeStarterCollections (line 6555) | function normalizeStarterCollections(collections) {
  function normalizeAreasFlag (line 6596) | function normalizeAreasFlag(value) {
  function readIfExists (line 6605) | function readIfExists(targetPath) {
  function hasGeneratedReadmeMarkers (line 6613) | function hasGeneratedReadmeMarkers(content) {
  function buildManagedReadmeSection (line 6618) | function buildManagedReadmeSection() {
  function ensureWorkspaceReadme (line 6647) | function ensureWorkspaceReadme(context, libraryName) {
  function ensureWorkspaceWorkAreasFile (line 6663) | function ensureWorkspaceWorkAreasFile(context, starterData) {
  function createStarterLibraryData (line 6672) | function createStarterLibraryData(libraryName, librarySlug, options = {}) {
  function initLibrary (line 6686) | function initLibrary(name, options = {}) {
  function buildDocs (line 6838) | function buildDocs(options = {}) {
  function collectCheckResults (line 6887) | function collectCheckResults(scope) {
  function checkSkills (line 6995) | function checkSkills(scope) {
  function main (line 7043) | async function main() {

FILE: lib/catalog-data.cjs
  constant VALID_CATEGORIES (line 7) | const VALID_CATEGORIES = ['development', 'document', 'creative', 'busine...
  constant VALID_DISTRIBUTIONS (line 8) | const VALID_DISTRIBUTIONS = ['bundled', 'live'];
  constant VALID_ORIGINS (line 9) | const VALID_ORIGINS = ['authored', 'curated', 'adapted'];
  constant VALID_SYNC_MODES (line 10) | const VALID_SYNC_MODES = ['authored', 'mirror', 'snapshot', 'adapted', '...
  constant VALID_TIERS (line 11) | const VALID_TIERS = ['house', 'upstream'];
  constant VALID_TRUST (line 12) | const VALID_TRUST = ['verified', 'reviewed', 'listed'];
  constant SKILL_NAME_PATTERN (line 13) | const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
  function getCatalogSkillNameValidationError (line 15) | function getCatalogSkillNameValidationError(name) {
  function isValidCatalogSkillName (line 28) | function isValidCatalogSkillName(name) {
  function deriveTier (line 32) | function deriveTier(skill) {
  function deriveDistribution (line 37) | function deriveDistribution(skill, tier) {
  function normalizeSkill (line 44) | function normalizeSkill(skill) {
  function normalizeCatalogData (line 68) | function normalizeCatalogData(data) {
  function resolveCatalogPath (line 77) | function resolveCatalogPath(context) {
  function loadCatalogData (line 81) | function loadCatalogData(context = null) {
  function writeCatalogData (line 86) | function writeCatalogData(data, context = null) {
  function findSkillByName (line 92) | function findSkillByName(data, skillName) {
  function getCatalogCounts (line 96) | function getCatalogCounts(data) {
  function validateCatalogData (line 107) | function validateCatalogData(data) {

FILE: lib/catalog-mutations.cjs
  constant VALID_TRUST (line 13) | const VALID_TRUST = ['listed', 'reviewed', 'verified'];
  constant CURATION_STALE_DAYS (line 14) | const CURATION_STALE_DAYS = 180;
  constant SUSPICIOUS_BRANCHES (line 15) | const SUSPICIOUS_BRANCHES = new Set(['general', 'misc', 'other', 'defaul...
  function currentIsoDay (line 17) | function currentIsoDay() {
  function currentCatalogTimestamp (line 21) | function currentCatalogTimestamp() {
  function normalizeListInput (line 25) | function normalizeListInput(value) {
  function ensureRequiredPlacement (line 38) | function ensureRequiredPlacement(fields, data) {
  function ensureCollectionIdsExist (line 59) | function ensureCollectionIdsExist(collectionIds, data) {
  function ensureValidTrust (line 72) | function ensureValidTrust(trust) {
  function buildRepoId (line 78) | function buildRepoId(parsed, fallbackSource = '') {
  function buildInstallSourceRef (line 85) | function buildInstallSourceRef(parsed, relativeDir) {
  function buildSourceUrl (line 101) | function buildSourceUrl(parsed, relativeDir) {
  function buildUpstreamCatalogEntry (line 113) | function buildUpstreamCatalogEntry({ source, parsed, discoveredSkill, fi...
  function buildHouseCatalogEntry (line 149) | function buildHouseCatalogEntry(fields, data) {
  function normalizeTrust (line 185) | function normalizeTrust(trust) {
  function resolveLastVerified (line 191) | function resolveLastVerified(fields, existingSkill = null) {
  function addSkillToCollections (line 207) | function addSkillToCollections(collections, skillName, collectionIds) {
  function removeSkillFromSelectedCollections (line 228) | function removeSkillFromSelectedCollections(collections, skillName, coll...
  function applyCurateChanges (line 242) | function applyCurateChanges(skill, changes, data) {
  function removeSkillFromCollections (line 317) | function removeSkillFromCollections(collections, skillName) {
  function commitCatalogData (line 324) | function commitCatalogData(rawData, context = null, options = {}) {
  function addUpstreamSkillFromDiscovery (line 350) | function addUpstreamSkillFromDiscovery({ source, parsed, discoveredSkill...
  function addHouseSkillEntry (line 374) | function addHouseSkillEntry(entry, context = null) {
  function curateSkill (line 389) | function curateSkill(skillName, changes, context = null) {
  function removeSkillFromCatalog (line 417) | function removeSkillFromCatalog(skillName, context = null) {
  function buildReviewQueue (line 432) | function buildReviewQueue(rawData, now = new Date()) {
  function generatedDocsStatus (line 476) | function generatedDocsStatus(context = null) {

FILE: lib/catalog-paths.cjs
  function getCatalogSkillRelativePath (line 4) | function getCatalogSkillRelativePath(skill) {
  function resolveCatalogSkillSourcePath (line 11) | function resolveCatalogSkillSourcePath(skillName, { sourceContext, skill...
  function hasLocalCatalogSkillFiles (line 19) | function hasLocalCatalogSkillFiles(skill, sourceContext) {
  function shouldTreatCatalogSkillAsHouse (line 24) | function shouldTreatCatalogSkillAsHouse(skill, sourceContext) {

FILE: lib/dependency-graph.cjs
  function normalizeRequires (line 1) | function normalizeRequires(value) {
  function buildDependencyGraph (line 18) | function buildDependencyGraph(data) {
  function getSkillDependencies (line 93) | function getSkillDependencies(data, skillName) {
  function getSkillDependents (line 98) | function getSkillDependents(data, skillName) {
  function resolveInstallOrder (line 103) | function resolveInstallOrder(data, requestedSkillNames) {

FILE: lib/frontmatter.cjs
  constant YAML (line 1) | const YAML = require('yaml');
  function parseSkillMarkdown (line 3) | function parseSkillMarkdown(raw) {

FILE: lib/install-metadata.cjs
  function parseRepoFromUrl (line 6) | function parseRepoFromUrl(url) {
  function normalizeInstalledMeta (line 12) | function normalizeInstalledMeta(meta = {}) {
  function writeInstalledMeta (line 36) | function writeInstalledMeta(skillPath, meta) {
  function readInstalledMeta (line 52) | function readInstalledMeta(skillPath) {

FILE: lib/install-state.cjs
  function getStandardInstallTargets (line 5) | function getStandardInstallTargets(cwd = process.cwd()) {
  function listInstalledSkillNamesInDir (line 21) | function listInstalledSkillNamesInDir(dirPath) {
  function buildInstallStateIndex (line 35) | function buildInstallStateIndex(options = {}) {
  function getInstallState (line 63) | function getInstallState(index, skillName) {
  function formatInstallStateLabel (line 92) | function formatInstallStateLabel(state) {
  function getInstalledSkillNames (line 96) | function getInstalledSkillNames(index, scope = null) {

FILE: lib/library-context.cjs
  constant WORKSPACE_DIR_NAME (line 6) | const WORKSPACE_DIR_NAME = '.ai-agent-skills';
  constant WORKSPACE_CONFIG_NAME (line 7) | const WORKSPACE_CONFIG_NAME = 'config.json';
  function createLibraryContext (line 9) | function createLibraryContext(rootDir, mode = 'bundled') {
  constant BUNDLED_LIBRARY_CONTEXT (line 27) | const BUNDLED_LIBRARY_CONTEXT = createLibraryContext(ROOT_DIR, 'bundled');
  function getBundledLibraryContext (line 29) | function getBundledLibraryContext() {
  function isManagedWorkspaceRoot (line 33) | function isManagedWorkspaceRoot(rootDir) {
  function resolveLibraryContext (line 41) | function resolveLibraryContext(startDir = process.cwd()) {
  function readWorkspaceConfig (line 57) | function readWorkspaceConfig(context) {

FILE: lib/paths.cjs
  constant ROOT_DIR (line 4) | const ROOT_DIR = path.join(__dirname, '..');
  constant SKILLS_DIR (line 5) | const SKILLS_DIR = path.join(ROOT_DIR, 'skills');
  constant SKILLS_JSON_PATH (line 6) | const SKILLS_JSON_PATH = path.join(ROOT_DIR, 'skills.json');
  constant README_PATH (line 7) | const README_PATH = path.join(ROOT_DIR, 'README.md');
  constant WORK_AREAS_PATH (line 8) | const WORK_AREAS_PATH = path.join(ROOT_DIR, 'WORK_AREAS.md');
  constant CONFIG_FILE (line 9) | const CONFIG_FILE = path.join(os.homedir(), '.agent-skills.json');
  constant SKILL_META_FILE (line 10) | const SKILL_META_FILE = '.skill-meta.json';
  constant MAX_SKILL_SIZE (line 11) | const MAX_SKILL_SIZE = 50 * 1024 * 1024;
  constant SCOPES (line 13) | const SCOPES = {
  constant LEGACY_AGENTS (line 18) | const LEGACY_AGENTS = {
  constant AGENT_PATHS (line 32) | const AGENT_PATHS = {

FILE: lib/render-docs.cjs
  constant README_MARKERS (line 6) | const README_MARKERS = {
  function formatTable (line 13) | function formatTable(headers, rows) {
  function escapeCell (line 20) | function escapeCell(value) {
  function sortSources (line 24) | function sortSources(skills) {
  function buildBundledLibraryStatsSection (line 33) | function buildBundledLibraryStatsSection(data) {
  function buildWorkspaceLibraryStatsSection (line 61) | function buildWorkspaceLibraryStatsSection(data) {
  function buildLibraryStatsSection (line 75) | function buildLibraryStatsSection(data, options = {}) {
  function buildShelfTableSection (line 83) | function buildShelfTableSection(data) {
  function buildCollectionTableSection (line 91) | function buildCollectionTableSection(data) {
  function buildSourceTableSection (line 107) | function buildSourceTableSection(data) {
  function replaceSection (line 115) | function replaceSection(content, markers, replacement) {
  function renderReadme (line 124) | function renderReadme(data, source, options = {}) {
  function renderWorkAreas (line 133) | function renderWorkAreas(data, options = {}) {
  function renderGeneratedDocs (line 178) | function renderGeneratedDocs(rawData, options = {}) {
  function generatedDocsAreInSync (line 188) | function generatedDocsAreInSync(rawData, options = {}) {
  function writeGeneratedDocs (line 200) | function writeGeneratedDocs(rawData, context = null) {
  function ensureTrailingNewline (line 209) | function ensureTrailingNewline(value) {
  function escapeRegex (line 214) | function escapeRegex(value) {

FILE: lib/source.cjs
  function sanitizeSubpath (line 8) | function sanitizeSubpath(subpath) {
  function isWindowsPath (line 19) | function isWindowsPath(source) {
  function isLocalPath (line 23) | function isLocalPath(source) {
  function isGitUrl (line 31) | function isGitUrl(source) {
  function parseGitUrl (line 39) | function parseGitUrl(source) {
  function getRepoNameFromUrl (line 49) | function getRepoNameFromUrl(url) {
  function validateGitUrl (line 62) | function validateGitUrl(url) {
  function sanitizeGitUrl (line 78) | function sanitizeGitUrl(url) {
  function expandPath (line 91) | function expandPath(p) {
  function parseSource (line 98) | function parseSource(source) {
  function classifyGitError (line 172) | function classifyGitError(message) {
  function discoverSkills (line 186) | function discoverSkills(rootDir, options = {}) {
  function prepareSource (line 272) | function prepareSource(source, options = {}) {

FILE: lib/workspace-import.cjs
  constant RESERVED_FLAT_DIRS (line 7) | const RESERVED_FLAT_DIRS = new Set([
  constant DEFAULT_CLASSIFY_KEYWORDS (line 16) | const DEFAULT_CLASSIFY_KEYWORDS = {
  constant IMPORT_AREA_ALIASES (line 24) | const IMPORT_AREA_ALIASES = {
  constant IMPORT_BRANCH_PREFIXES (line 35) | const IMPORT_BRANCH_PREFIXES = {
  function readSkillCandidate (line 43) | function readSkillCandidate(dirPath, layout) {
  function listChildDirectories (line 91) | function listChildDirectories(rootDir) {
  function humanizeAreaId (line 101) | function humanizeAreaId(id) {
  function humanizeToken (line 109) | function humanizeToken(token) {
  function tokenizeText (line 117) | function tokenizeText(value) {
  function countMatches (line 125) | function countMatches(text, token) {
  function countSubstringMatches (line 132) | function countSubstringMatches(text, token) {
  function discoverImportCandidates (line 149) | function discoverImportCandidates(rootDir) {
  function classifyImportedSkill (line 227) | function classifyImportedSkill(candidate, workAreas = []) {
  function inferImportedBranch (line 329) | function inferImportedBranch(candidate, workArea, firstTokenCounts = new...
  function phraseFromDescription (line 348) | function phraseFromDescription(description) {
  function buildImportedWhyHere (line 370) | function buildImportedWhyHere(candidate, classification) {
  function buildWorkAreaDistribution (line 379) | function buildWorkAreaDistribution(imported = []) {

FILE: scripts/test-live.js
  function info (line 24) | function info(message) {
  function pass (line 28) | function pass(message) {
  function warn (line 32) | function warn(message) {
  function fail (line 36) | function fail(message) {
  function parseArgs (line 40) | function parseArgs(argv) {
  function ensure (line 85) | function ensure(condition, message) {
  function sha256 (line 91) | function sha256(value) {
  function sanitizeForReport (line 95) | function sanitizeForReport(text) {
  function runCommand (line 101) | function runCommand(command, args, options = {}) {
  function runCli (line 123) | function runCli(args, options = {}) {
  function runExpect (line 131) | function runExpect(script, options = {}) {
  function maybeMkdir (line 135) | function maybeMkdir(dirPath) {
  function removeDir (line 139) | function removeDir(dirPath) {
  function listFilesRecursive (line 143) | function listFilesRecursive(rootDir, relativePrefix = '') {
  function snapshotDirectory (line 166) | function snapshotDirectory(dirPath, { excludeMeta = false } = {}) {
  function compareSnapshots (line 191) | function compareSnapshots(sourceSnapshot, installedSnapshot, contextLabe...
  function repoIdFromSource (line 211) | function repoIdFromSource(source) {
  function getSkillSourceDir (line 219) | function getSkillSourceDir(skill, repoCache) {
  function collectSourceSnapshot (line 254) | function collectSourceSnapshot(skill, repoCache) {
  function pickQuickSkills (line 287) | function pickQuickSkills(catalog) {
  function selectSkills (line 298) | function selectSkills(catalog, options) {
  function createIsolatedContext (line 311) | function createIsolatedContext(options = {}) {
  function createPrivateLibraryFixture (line 332) | function createPrivateLibraryFixture() {
  function expectedInstallDir (line 369) | function expectedInstallDir(scope, context, skillName) {
  function runWorkspaceBrowseSmoke (line 376) | function runWorkspaceBrowseSmoke(env, cwd, expectedLines) {
  function runPrivateLibraryScenario (line 391) | function runPrivateLibraryScenario() {
  function verifyInstalledMeta (line 470) | function verifyInstalledMeta(skill, scope, meta) {
  function runPreviewFlow (line 487) | function runPreviewFlow(skill) {
  function runCatalogListFlow (line 500) | function runCatalogListFlow(sourceRepo, expectedSkills) {
  function runInstallLifecycle (line 515) | function runInstallLifecycle(skill, sourceSnapshot, scope) {
  function runCollectionInstallFlow (line 588) | function runCollectionInstallFlow(collectionId, expectedSkills) {
  function resolveExpectBinary (line 620) | function resolveExpectBinary() {
  function runTuiSmoke (line 627) | function runTuiSmoke(env) {
  function runTuiHomeSnapshot (line 640) | function runTuiHomeSnapshot(env, { columns, rows, expectedLines }) {
  function runTuiDetailSnapshot (line 655) | function runTuiDetailSnapshot(skillName, env, cwd) {
  function runTuiInstall (line 689) | function runTuiInstall(skillName, scope, env, cwd) {
  function runPackSmoke (line 734) | function runPackSmoke() {
  function cacheUpstreamRepos (line 746) | function cacheUpstreamRepos(skills, report) {
  function cleanupRepoCache (line 781) | function cleanupRepoCache(repoCache) {
  function main (line 789) | async function main() {

FILE: scripts/validate.js
  function error (line 22) | function error(msg) { console.error(`  \x1b[31m✗\x1b[0m ${msg}`); errors...
  function warn (line 23) | function warn(msg) { console.warn(`  \x1b[33m!\x1b[0m ${msg}`); warnings...
  function pass (line 24) | function pass(msg) { console.log(`  \x1b[32m✓\x1b[0m ${msg}`); }

FILE: test.js
  function test (line 29) | function test(name, fn) {
  function assert (line 41) | function assert(condition, message) {
  function assertEqual (line 45) | function assertEqual(a, b, message) {
  function assertContains (line 49) | function assertContains(str, substr, message) {
  function assertNotContains (line 53) | function assertNotContains(str, substr, message) {
  function parseJsonLines (line 57) | function parseJsonLines(output) {
  function withDefaultFormat (line 65) | function withDefaultFormat(args, options = {}) {
  function run (line 72) | function run(cmd) {
  function runArgs (line 81) | function runArgs(args) {
  function runArgsWithOptions (line 89) | function runArgsWithOptions(args, options = {}) {
  function runModule (line 101) | function runModule(source) {
  function runCommandResult (line 109) | function runCommandResult(args, options = {}) {
  function copyValidateFixtureFiles (line 128) | function copyValidateFixtureFiles(tmpDir) {
  function writeFixtureDocs (line 143) | function writeFixtureDocs(tmpDir, data) {
  function snapshotCatalogFiles (line 168) | function snapshotCatalogFiles() {
  function restoreCatalogFiles (line 176) | function restoreCatalogFiles(snapshot) {
  function slugifyName (line 182) | function slugifyName(name) {
  function createWorkspaceFixture (line 190) | function createWorkspaceFixture(libraryName = 'Workspace Test') {
  function seedWorkspaceCatalog (line 209) | function seedWorkspaceCatalog(workspaceDir) {
  function initGitRepo (line 265) | function initGitRepo(repoDir) {
  function createLocalSkillRepo (line 271) | function createLocalSkillRepo(skillName, description = 'Fixture skill') {
  function createFlatSkillLibraryFixture (line 283) | function createFlatSkillLibraryFixture(skillDefs = []) {

FILE: tui/catalog.cjs
  constant SKILLS_CLI_VERSION (line 11) | const SKILLS_CLI_VERSION = 'skills@1.4.5';
  constant SOURCE_TITLES (line 13) | const SOURCE_TITLES = {
  constant SKILLS_AGENT_MAP (line 22) | const SKILLS_AGENT_MAP = {
  constant TOKEN_TITLES (line 35) | const TOKEN_TITLES = {
  function titleizeToken (line 52) | function titleizeToken(token) {
  function humanizeSlug (line 59) | function humanizeSlug(slug) {
  function sourceTitle (line 67) | function sourceTitle(source) {
  function readSkillsJson (line 71) | function readSkillsJson(context) {
  function readSkillMarkdown (line 75) | function readSkillMarkdown(skillName, context, skill = null) {
  function buildSearchText (line 84) | function buildSearchText(parts) {
  function buildCollectionPlacementMap (line 91) | function buildCollectionPlacementMap(collections) {
  function getSkillOriginRank (line 103) | function getSkillOriginRank(skill) {
  function getSkillTrustRank (line 109) | function getSkillTrustRank(skill) {
  function getSkillCurationScore (line 115) | function getSkillCurationScore(collectionPlacement, skill) {
  function compareSkillsByCuration (line 126) | function compareSkillsByCuration(collectionPlacement, left, right) {
  function sortSkillsByCuration (line 148) | function sortSkillsByCuration(data, skills) {
  function compareSkillsByCurationData (line 153) | function compareSkillsByCurationData(data, left, right) {
  function getSiblingRecommendations (line 158) | function getSiblingRecommendations(data, skill, limit = 3) {
  function shellQuote (line 203) | function shellQuote(value) {
  function getSkillsAgent (line 209) | function getSkillsAgent(agent) {
  function getSkillsInstallSpec (line 213) | function getSkillsInstallSpec(skill, agent) {
  function getGitHubTreePath (line 261) | function getGitHubTreePath(sourceUrl, source) {
  function getGitHubInstallSource (line 283) | function getGitHubInstallSource(skill) {
  function getGitHubInstallSpec (line 303) | function getGitHubInstallSpec(skill, agent) {
  function buildCatalog (line 317) | function buildCatalog(context = resolveLibraryContext()) {
  function getInstallCommand (line 546) | function getInstallCommand(skill, scope) {
  function getInstallCommandForAgent (line 551) | function getInstallCommandForAgent(skill, agent) {

FILE: tui/index.mjs
  constant CLI_PATH (line 16) | const CLI_PATH = require.resolve('../cli.js');
  constant THEMES (line 18) | const THEMES = [
  constant COLORS (line 96) | const COLORS = {...THEMES[0].colors};
  constant SOURCE_NOTES (line 98) | const SOURCE_NOTES = {
  constant CREATOR_HANDLE (line 107) | const CREATOR_HANDLE = '@moizibnyousaf';
  constant LIBRARY_SIGNATURE (line 108) | const LIBRARY_SIGNATURE = "Moiz's Curated Agent Skills Library";
  constant LIBRARY_THESIS (line 109) | const LIBRARY_THESIS = 'Start with a shelf.';
  constant LIBRARY_SUPPORT (line 110) | const LIBRARY_SUPPORT = 'A smaller library, kept by hand.';
  function clamp (line 112) | function clamp(value, min, max) {
  function applyTheme (line 116) | function applyTheme(themeIndex) {
  function parsePositiveNumber (line 122) | function parsePositiveNumber(value) {
  function readTerminalMetric (line 127) | function readTerminalMetric(name) {
  function resolveTerminalSize (line 137) | function resolveTerminalSize(stdout) {
  function wait (line 153) | function wait(milliseconds) {
  function waitForStableTerminalSize (line 159) | async function waitForStableTerminalSize(stdout, attempts = 4, intervalM...
  function enterInteractiveScreen (line 174) | function enterInteractiveScreen(stdout) {
  function getViewportProfile (line 199) | function getViewportProfile({columns, rows}) {
  function getReservedRows (line 222) | function getReservedRows(screen, viewport, {showInspector = false} = {}) {
  function fitText (line 249) | function fitText(text, maxLength) {
  function formatCount (line 255) | function formatCount(count, singular, plural = `${singular}s`) {
  function shellQuote (line 259) | function shellQuote(value) {
  function commandLabel (line 267) | function commandLabel(parts) {
  function stripFrontmatter (line 271) | function stripFrontmatter(markdown) {
  function excerpt (line 279) | function excerpt(markdown, lines = 12) {
  function compactText (line 287) | function compactText(text, maxLength) {
  function sourceNoteFor (line 291) | function sourceNoteFor(sourceSlug, fallback = '') {
  function getColumnsPerRow (line 295) | function getColumnsPerRow(columns, mode = 'default') {
  function getAtlasTileHeight (line 308) | function getAtlasTileHeight(mode = 'default', compact = false) {
  function moveGrid (line 316) | function moveGrid(index, key, itemCount, columnsPerRow) {
  function getViewportState (line 325) | function getViewportState({items, selectedIndex, columns, rows, mode = '...
  function Header (line 361) | function Header({breadcrumbs, title, subtitle, hint, metaItems = [], vie...
  function ModeTabs (line 408) | function ModeTabs({rootMode, compact = false}) {
  function FooterBar (line 432) | function FooterBar({hint, detail = 'Curated library', mode = 'ATLAS', co...
  function MetricLine (line 457) | function MetricLine({items}) {
  function ChipRow (line 469) | function ChipRow({items, selected, compact = false}) {
  function AtlasTile (line 489) | function AtlasTile({
  function AtlasGrid (line 579) | function AtlasGrid({items, selectedIndex, columns, rows, mode = 'default...
  function getStripState (line 628) | function getStripState({items, selectedIndex, columns, mode = 'default',...
  function ShelfStrip (line 653) | function ShelfStrip({items, selectedIndex, columns, mode = 'default', ac...
  function getHeroHighlights (line 690) | function getHeroHighlights(section, selectedItem, selectedIndex = 0, lim...
  function ShelfHero (line 707) | function ShelfHero({section, selectedItem, columns, selectedIndex = 0, v...
  function formatPreviewLines (line 746) | function formatPreviewLines(markdown, maxLines = 12) {
  function SearchOverlay (line 795) | function SearchOverlay({query, setQuery, results, selectedIndex, columns...
  function HelpOverlay (line 832) | function HelpOverlay({viewport = null}) {
  function PaletteOverlay (line 850) | function PaletteOverlay({query, setQuery, items, selectedIndex, viewport...
  function TextEntryOverlay (line 883) | function TextEntryOverlay({title, subtitle, value, setValue, viewport = ...
  function MenuOverlay (line 899) | function MenuOverlay({title, subtitle, items, selectedIndex, viewport = ...
  function ReviewOverlay (line 922) | function ReviewOverlay({entries, selectedIndex, viewport = null}) {
  function Inspector (line 954) | function Inspector({title, eyebrow, lines, command, footer, variant = 'c...
  function ActionBar (line 1005) | function ActionBar({items}) {
  function ModalShell (line 1018) | function ModalShell({title, subtitle, width = 84, children, footerLines ...
  function ModalOption (line 1045) | function ModalOption({label, meta = '', description = '', selected}) {
  function SkillScreen (line 1062) | function SkillScreen({skill, previewMode, scope, agent, columns, viewpor...
  function InstallChooser (line 1283) | function InstallChooser({skill, scope, agent, selectedIndex, columns, vi...
  function buildBreadcrumbs (line 1394) | function buildBreadcrumbs(rootMode, stack, catalog) {
  function getCollectionItems (line 1450) | function getCollectionItems(catalog) {
  function getShelfItems (line 1463) | function getShelfItems(catalog) {
  function getTierSkillItems (line 1484) | function getTierSkillItems(catalog, tier, limit = 6) {
  function getTierLabel (line 1492) | function getTierLabel(skill) {
  function getDistributionLabel (line 1496) | function getDistributionLabel(skill) {
  function getInstallSummary (line 1500) | function getInstallSummary(skill) {
  function getSkillProvenanceLines (line 1506) | function getSkillProvenanceLines(skill, {wide = false} = {}) {
  function getNeighboringPickLines (line 1518) | function getNeighboringPickLines(relatedSkills) {
  function getSourceItems (line 1526) | function getSourceItems(catalog) {
  function getSkillCardChips (line 1539) | function getSkillCardChips(skill, fallbackChips = []) {
  function getAreaItems (line 1546) | function getAreaItems(area) {
  function getSourceBranchItems (line 1561) | function getSourceBranchItems(source) {
  function getSkillItems (line 1575) | function getSkillItems(skills) {
  function getCollectionSkillItems (line 1587) | function getCollectionSkillItems(collection) {
  function getInstalledItems (line 1599) | function getInstalledItems(catalog) {
  function filterPaletteItems (line 1605) | function filterPaletteItems(items, query) {
  function runCliMutation (line 1611) | function runCliMutation(args) {
  function maybeRenameRootSkillForRepo (line 1618) | function maybeRenameRootSkillForRepo(discovered, parsed, rootDir, repoRo...
  function discoverSourceSkillsForCatalog (line 1641) | function discoverSourceSkillsForCatalog(source) {
  function App (line 1671) | function App({catalog: initialCatalog, scope, agent, onExit, libraryCont...
  function launchTui (line 3119) | async function launchTui({agent = null, scope = 'global'} = {}) {
Condensed preview — 64 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,215K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 60,
    "preview": "github: MoizIbnYousaf\ncustom: [\"https://moizibnyousaf.com\"]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "chars": 1104,
    "preview": "name: Bug Report\ndescription: Report an issue with a skill or install flow\ntitle: \"[Bug] \"\nlabels: [\"bug\"]\nbody:\n  - typ"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 373,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Browse Collections\n    url: https://github.com/MoizIbnYousaf/Ai-Age"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/skill-request.yml",
    "chars": 2307,
    "preview": "name: Skill Request\ndescription: Suggest a skill to add\ntitle: \"[Curation] \"\nlabels: [\"skill-request\"]\nbody:\n  - type: m"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 801,
    "preview": "## Summary\n\nBrief description of the change.\n\n## Type\n\n- [ ] New skill\n- [ ] Skill update/fix\n- [ ] Documentation\n- [ ] "
  },
  {
    "path": ".github/workflows/validate.yml",
    "chars": 716,
    "preview": "name: Validate Skills\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  validate:\n    runs"
  },
  {
    "path": ".gitignore",
    "chars": 544,
    "preview": "# Dependencies\nnode_modules/\n\n# Build outputs\ndist/\nbuild/\n\n# OS files\n.DS_Store\nThumbs.db\n\n# IDE\n.vscode/\n.idea/\n.curso"
  },
  {
    "path": ".npmignore",
    "chars": 341,
    "preview": "# Development files\n.git\n.github\n.cursor\n.gitignore\n.npmignore\n\n# Test files\ntest.js\nscripts/\n\n# Local install artifacts"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 14457,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [4.2.0] - 2026-03-31\n\n### Added\n- "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2422,
    "preview": "# Contributing to AI Agent Skills\n\nThis repo is curated.\n\nMost of the library is sourced from other repos, so attributio"
  },
  {
    "path": "CURATION.md",
    "chars": 4040,
    "preview": "# Curation Guide\n\nThis is my keep pile.\n\nI am not trying to mirror every agent skill on the internet. I want a strong se"
  },
  {
    "path": "FOR_YOUR_AGENT.md",
    "chars": 11453,
    "preview": "# For Your Agent\n\nUse this when you want an agent to build and share a managed skills library for you, not just make a l"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2025 Moiz Ibn Yousaf\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "README.md",
    "chars": 17094,
    "preview": "<h1 align=\"center\">AI Agent Skills</h1>\n\n<p align=\"center\">\n  <strong>My curated library of agent skills, plus the packa"
  },
  {
    "path": "WORK_AREAS.md",
    "chars": 4717,
    "preview": "# Work Areas\n\nShelf map for the library.\n\nHouse copies stay flat under `skills/<name>/`. The catalog holds the real stru"
  },
  {
    "path": "atlas.html",
    "chars": 17945,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initia"
  },
  {
    "path": "cli.js",
    "chars": 258084,
    "preview": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\nconst os = require('os');\nconst readline = "
  },
  {
    "path": "curator.html",
    "chars": 22854,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initia"
  },
  {
    "path": "lib/catalog-data.cjs",
    "chars": 7865,
    "preview": "const fs = require('fs');\n\nconst { SKILLS_JSON_PATH } = require('./paths.cjs');\nconst { getBundledLibraryContext } = req"
  },
  {
    "path": "lib/catalog-mutations.cjs",
    "chars": 16809,
    "preview": "const fs = require('fs');\n\nconst {\n  findSkillByName,\n  loadCatalogData,\n  normalizeCatalogData,\n  normalizeSkill,\n  val"
  },
  {
    "path": "lib/catalog-paths.cjs",
    "chars": 1200,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\nfunction getCatalogSkillRelativePath(skill) {\n  if (skill && ty"
  },
  {
    "path": "lib/dependency-graph.cjs",
    "chars": 3868,
    "preview": "function normalizeRequires(value) {\n  if (!Array.isArray(value)) return [];\n\n  const seen = new Set();\n  const output = "
  },
  {
    "path": "lib/frontmatter.cjs",
    "chars": 530,
    "preview": "const YAML = require('yaml');\n\nfunction parseSkillMarkdown(raw) {\n  const input = String(raw || '');\n  const match = inp"
  },
  {
    "path": "lib/install-metadata.cjs",
    "chars": 1811,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\nconst { SKILL_META_FILE } = require('./paths.cjs');\n\nfunction p"
  },
  {
    "path": "lib/install-state.cjs",
    "chars": 2536,
    "preview": "const fs = require('fs');\nconst os = require('os');\nconst path = require('path');\n\nfunction getStandardInstallTargets(cw"
  },
  {
    "path": "lib/library-context.cjs",
    "chars": 2082,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\nconst { ROOT_DIR } = require('./paths.cjs');\n\nconst WORKSPACE_D"
  },
  {
    "path": "lib/paths.cjs",
    "chars": 1539,
    "preview": "const os = require('os');\nconst path = require('path');\n\nconst ROOT_DIR = path.join(__dirname, '..');\nconst SKILLS_DIR ="
  },
  {
    "path": "lib/render-docs.cjs",
    "chars": 9165,
    "preview": "const fs = require('fs');\n\nconst { getBundledLibraryContext, readWorkspaceConfig } = require('./library-context.cjs');\nc"
  },
  {
    "path": "lib/source.cjs",
    "chars": 11481,
    "preview": "const fs = require('fs');\nconst os = require('os');\nconst path = require('path');\nconst { execFileSync } = require('chil"
  },
  {
    "path": "lib/workspace-import.cjs",
    "chars": 11168,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\nconst { getCatalogSkillNameValidationError } = require('./catal"
  },
  {
    "path": "package.json",
    "chars": 1278,
    "preview": "{\n  \"name\": \"ai-agent-skills\",\n  \"version\": \"4.2.0\",\n  \"description\": \"Curated agent skills library and library manager "
  },
  {
    "path": "scripts/render-docs.js",
    "chars": 275,
    "preview": "#!/usr/bin/env node\n\nconst { loadCatalogData } = require('../lib/catalog-data.cjs');\nconst { writeGeneratedDocs } = requ"
  },
  {
    "path": "scripts/test-live.js",
    "chars": 34654,
    "preview": "#!/usr/bin/env node\n\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst os = require('os');\nconst path = "
  },
  {
    "path": "scripts/validate.js",
    "chars": 5537,
    "preview": "#!/usr/bin/env node\n\n/**\n * Catalog validation for ai-agent-skills.\n * Checks skills.json integrity, folder structure, a"
  },
  {
    "path": "scripts/vendor.js",
    "chars": 534,
    "preview": "#!/usr/bin/env node\n\nconst path = require('path');\nconst { spawnSync } = require('child_process');\n\nconst cliPath = path"
  },
  {
    "path": "skills/ask-questions-if-underspecified/SKILL.md",
    "chars": 3776,
    "preview": "---\nname: ask-questions-if-underspecified\ndescription: Clarify requirements before implementing. Do not use automaticall"
  },
  {
    "path": "skills/audit-library-health/SKILL.md",
    "chars": 2069,
    "preview": "---\nname: audit-library-health\ndescription: Use when checking the overall health of a skills library. Run doctor, valida"
  },
  {
    "path": "skills/backend-development/SKILL.md",
    "chars": 3410,
    "preview": "---\nname: backend-development\ndescription: Backend API design, database architecture, microservices patterns, and test-d"
  },
  {
    "path": "skills/best-practices/SKILL.md",
    "chars": 16824,
    "preview": "---\nname: best-practices\ndescription: >-\n  Transforms vague prompts into optimized Claude Code prompts. Adds verificatio"
  },
  {
    "path": "skills/best-practices/agents/best-practices-referencer.md",
    "chars": 9370,
    "preview": "---\nname: best-practices-referencer\ndescription: >-\n  Use this agent to find relevant best practices, examples, and anti"
  },
  {
    "path": "skills/best-practices/agents/codebase-context-builder.md",
    "chars": 10352,
    "preview": "---\nname: codebase-context-builder\ndescription: >-\n  Use this agent to gather codebase context for prompt transformation"
  },
  {
    "path": "skills/best-practices/agents/task-intent-analyzer.md",
    "chars": 9810,
    "preview": "---\nname: task-intent-analyzer\ndescription: >-\n  Use this agent to deeply analyze a prompt's intent before transformatio"
  },
  {
    "path": "skills/best-practices/references/anti-patterns.md",
    "chars": 13591,
    "preview": "# Prompt Anti-Patterns to Avoid\n\nThis document catalogs common prompt mistakes and how to fix them. When transforming pr"
  },
  {
    "path": "skills/best-practices/references/before-after-examples.md",
    "chars": 27803,
    "preview": "# Before/After Prompt Transformation Examples\n\nThis document contains 50+ examples of prompt transformations organized b"
  },
  {
    "path": "skills/best-practices/references/best-practices-guide.md",
    "chars": 32616,
    "preview": "# Best Practices for Claude Code\n\n> Tips and patterns for getting the most out of Claude Code, from configuring your env"
  },
  {
    "path": "skills/best-practices/references/common-workflows.md",
    "chars": 14159,
    "preview": "# Common Workflow Prompts\n\nThis document contains optimized prompts for common development workflows. Use these as templ"
  },
  {
    "path": "skills/best-practices/references/prompt-patterns.md",
    "chars": 11672,
    "preview": "# Prompt Transformation Patterns\n\nThis document contains reusable templates and patterns for transforming prompts. Use t"
  },
  {
    "path": "skills/browse-and-evaluate/SKILL.md",
    "chars": 1809,
    "preview": "---\nname: browse-and-evaluate\ndescription: Use when exploring the ai-agent-skills catalog to find, compare, and evaluate"
  },
  {
    "path": "skills/build-workspace-docs/SKILL.md",
    "chars": 1540,
    "preview": "---\nname: build-workspace-docs\ndescription: Use when regenerating README.md and WORK_AREAS.md in a managed library works"
  },
  {
    "path": "skills/changelog-generator/SKILL.md",
    "chars": 3101,
    "preview": "---\nname: changelog-generator\ndescription: Automatically creates user-facing changelogs from git commits by analyzing co"
  },
  {
    "path": "skills/code-documentation/SKILL.md",
    "chars": 5762,
    "preview": "---\nname: code-documentation\ndescription: Writing effective code documentation - API docs, README files, inline comments"
  },
  {
    "path": "skills/content-research-writer/SKILL.md",
    "chars": 14170,
    "preview": "---\nname: content-research-writer\ndescription: Assists in writing high-quality content by conducting research, adding ci"
  },
  {
    "path": "skills/curate-a-team-library/SKILL.md",
    "chars": 3892,
    "preview": "---\nname: curate-a-team-library\ndescription: Use when building a managed team skills library for a real stack. Map work "
  },
  {
    "path": "skills/database-design/SKILL.md",
    "chars": 4632,
    "preview": "---\nname: database-design\ndescription: Database schema design, optimization, and migration patterns for PostgreSQL, MySQ"
  },
  {
    "path": "skills/install-from-remote-library/SKILL.md",
    "chars": 2121,
    "preview": "---\nname: install-from-remote-library\ndescription: Use when installing skills from a shared ai-agent-skills library repo"
  },
  {
    "path": "skills/llm-application-dev/SKILL.md",
    "chars": 5109,
    "preview": "---\nname: llm-application-dev\ndescription: Building applications with Large Language Models - prompt engineering, RAG pa"
  },
  {
    "path": "skills/migrate-skills-between-libraries/SKILL.md",
    "chars": 2671,
    "preview": "---\nname: migrate-skills-between-libraries\ndescription: Use when moving skills between library workspaces or upgrading f"
  },
  {
    "path": "skills/review-a-skill/SKILL.md",
    "chars": 2304,
    "preview": "---\nname: review-a-skill\ndescription: Use when evaluating whether a skill belongs in a library. Preview content, check f"
  },
  {
    "path": "skills/share-a-library/SKILL.md",
    "chars": 1647,
    "preview": "---\nname: share-a-library\ndescription: Use when a managed library is ready to publish to GitHub and hand to teammates as"
  },
  {
    "path": "skills/update-installed-skills/SKILL.md",
    "chars": 1570,
    "preview": "---\nname: update-installed-skills\ndescription: Use when syncing or updating previously installed skills to their latest "
  },
  {
    "path": "skills.json",
    "chars": 155859,
    "preview": "{\n  \"version\": \"4.2.0\",\n  \"updated\": \"2026-03-31T00:00:00Z\",\n  \"total\": 110,\n  \"workAreas\": [\n    {\n      \"id\": \"fronten"
  },
  {
    "path": "test.js",
    "chars": 196599,
    "preview": "#!/usr/bin/env node\n\n/**\n * Test suite for ai-agent-skills CLI\n * Run with: node test.js\n */\n\nconst fs = require('fs');\n"
  },
  {
    "path": "tui/catalog.cjs",
    "chars": 18207,
    "preview": "const fs = require('fs');\nconst path = require('path');\nconst { loadCatalogData } = require('../lib/catalog-data.cjs');\n"
  },
  {
    "path": "tui/index.mjs",
    "chars": 114256,
    "preview": "import React, {useEffect, useMemo, useState} from 'react';\nimport {createRequire} from 'module';\nimport {spawnSync} from"
  }
]

About this extraction

This page contains the full source code of the skillcreatorai/Ai-Agent-Skills GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 64 files (1.1 MB), approximately 283.8k tokens, and a symbol index with 575 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!