Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b17a66e
docs: define provider-native skillpack refactor
WTW0313 Jun 30, 2026
c87c649
chore: update .gitignore to include .agents and skills-lock.json
WTW0313 Jun 30, 2026
2492dc0
chore: configure pnpm build approvals
WTW0313 Jun 30, 2026
d96f1d2
feat(core): build grouped skill inventory
WTW0313 Jun 30, 2026
c981e55
feat(tui): add read-only project skills view
WTW0313 Jun 30, 2026
797b8d3
refactor: remove skill authoring flows
WTW0313 Jun 30, 2026
81896ec
feat(core): route toggles by provider instance
WTW0313 Jun 30, 2026
a7029b5
feat(tui): install skills through skillssh only
WTW0313 Jun 30, 2026
ce41004
feat(tui): add manual skillssh updates view
WTW0313 Jun 30, 2026
3d11ea1
fix(core): restrict removal to skillssh globals
WTW0313 Jun 30, 2026
446544c
feat(core): expose configurable scan roots
WTW0313 Jun 30, 2026
424e380
feat(core): use provider-native skill availability
WTW0313 Jun 30, 2026
b56fafa
docs: clarify provider-native skill management
WTW0313 Jun 30, 2026
4ac4c13
fix(core): remove unsupported github source paths
WTW0313 Jun 30, 2026
474fbfb
fix(core): prevent toggling shared global skills
WTW0313 Jun 30, 2026
d63fcd0
docs: define Codex plugin skill ownership
WTW0313 Jul 1, 2026
5dea3cb
feat(core): manage Codex plugin skills
WTW0313 Jul 1, 2026
85a8d49
feat(tui): confirm Codex plugin toggles
WTW0313 Jul 1, 2026
4a9ac17
feat(tui): add read-only settings view
WTW0313 Jul 1, 2026
bc540fc
fix: Remove pinned pnpm version from CI setup
WTW0313 Jul 1, 2026
a232527
fix: Bump CI Node.js version to 22
WTW0313 Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ jobs:

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.32.1

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: pnpm

- name: Install dependencies
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules/
dist/
*.tsbuildinfo
.pnpm-store/
.agents
skills-lock.json
73 changes: 39 additions & 34 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@

## Project Overview

Skillpack is a unified TUI manager for agent skills across Codex, Cursor, Claude, and Global (`~/.agents/skills`). It's a pnpm monorepo with two packages:
Skillpack is a unified TUI manager for agent skills across Codex, Claude, and Global (`~/.agents/skills`). It's a pnpm monorepo with two packages:

- `packages/core` (`@skillpack/core`) — platform-agnostic library: skill scanning, providers, install sources, parser, lockfile
- `packages/tui` (`@skillpack/tui`) — Ink (React) terminal UI with keyboard-driven navigation

## Agent skills

### Issue tracker

Issues are tracked in GitHub Issues; external PRs are not a triage request surface. See `docs/agents/issue-tracker.md`.

### Triage labels

The default triage label vocabulary is used: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.

### Domain docs

This repo uses a single-context domain docs layout. See `docs/agents/domain.md`.

## Tech Stack

- **Language**: TypeScript (ES2022, Node16 modules, strict mode)
Expand Down Expand Up @@ -34,33 +48,34 @@ node packages/tui/dist/bin/skillpack.js
| File | Purpose |
|------|---------|
| `manager.ts` | `SkillManager` — central orchestrator for scan, toggle, install, uninstall, update |
| `providers/provider.ts` | `ISkillProvider` interface + `BaseProvider` with `.disabled-` prefix toggle |
| `providers/{codex,cursor,claude,global}.ts` | Per-agent provider implementations |
| `providers/provider.ts` | `ISkillProvider` interface + `BaseProvider` with fallback `.disabled-` prefix toggle |
| `providers/{codex,claude,global}.ts` | Default provider implementations |
| `duplicates.ts` | `DuplicateDetector` — finds same-name skills across providers (symlink-aware) |
| `models/skill.ts` | `Skill`, `SkillTemplate`, `SkillSource` types (no `readonly` flag — all skills are editable/deletable) |
| `models/skill.ts` | `Skill`, `SkillTemplate`, `SkillSource` types |
| `models/duplicate.ts` | `DuplicateInfo`, `DuplicateInstance` types |
| `parser.ts` | SKILL.md YAML frontmatter parser |
| `config.ts` | Configuration manager (`~/.config/skillpack/config.json`) |
| `lockfile.ts` | `LockfileManager` — `~/.config/skillpack/skillpack.lock` for GitHub-installed skills |
| `skills-lock.ts` | `SkillsLockReader` — read-only reader for skills.sh's `~/.agents/.skill-lock.json` |
| `sources/` | Remote install sources (GitHub, skills.sh) |
| `sources/` | Remote install sources (skills.sh only in v1) |

### TUI (`packages/tui/src/`)

| File | Purpose |
|------|---------|
| `app.tsx` | App shell, router by `view` state |
| `context/app-context.tsx` | Global state: skills, duplicates, selectedSkill, view, refresh |
| `views/list-view.tsx` | Main list with tabs, search, scroll |
| `views/detail-view.tsx` | Skill detail: metadata, source info, update check (`u`), toggle/edit/delete |
| `views/install-view.tsx` | Remote install flow (source → query → results → install to Global) |
| `views/create-view.tsx` | Skill creation wizard |
| `views/list-view.tsx` | Main inventory with tabs, search, scroll |
| `views/detail-view.tsx` | Skill detail: metadata, source info, toggle, skills.sh update/remove |
| `views/project-skills-view.tsx` | Read-only Project Skills view |
| `views/settings-view.tsx` | Read-only Settings view for Scan Roots, providers, and sources |
| `views/install-view.tsx` | skills.sh install flow (query → results → install to Global) |
| `views/updates-view.tsx` | Manual skills.sh update checks |
| `components/` | StatusBar (context-aware shortcuts), ConfirmDialog, SkillRow, TabBar, SearchInput |
| `bin/skillpack.ts` | CLI entry point with alternate screen buffer |

### Routing

The TUI uses a `view` state (`'list' | 'detail' | 'install' | 'create'`) in `app-context.tsx`, not a router library. The `Router` component in `app.tsx` switches on this state.
The TUI uses a `view` state (`'list' | 'detail' | 'install' | 'project' | 'settings' | 'updates'`) in `app-context.tsx`, not a router library. The `Router` component in `app.tsx` switches on this state.

## Conventions

Expand All @@ -71,24 +86,20 @@ The TUI uses a `view` state (`'list' | 'detail' | 'install' | 'create'`) in `app
- Prefer `useMemo` for derived state in React components
- Use `useInput` from Ink for keyboard handling with `isActive` to scope input

### Skill Toggle Mechanism
### Skill Availability And Toggle

Skills are toggled by renaming their directory with a `.disabled-` prefix. The `enable`/`disable` methods on providers handle this. When calling toggle from the manager, always use the actual directory name from `skill.path` (via `path.basename()`), never `skill.name`, because the SKILL.md `name` field can differ from the directory name.
Skill discovery and Skill Availability are separate facts. A skill can exist on disk while a provider config marks it unavailable. Providers should read their provider-native config files to decide `skill.enabled` and should toggle by editing provider config when a known config mechanism exists. Codex provider-local skills use `[[skills.config]]` entries in `~/.codex/config.toml` keyed by absolute `SKILL.md` path. Codex Plugin-Owned Skills use `[plugins."plugin-name@marketplace-name"]` in `~/.codex/config.toml`; their availability is `plugin enabled AND skill config not false`, and toggling one toggles the owning plugin for all sibling skills. Claude regular skills use `skillOverrides` in Claude `settings.json`, and Claude plugin skills use `enabledPlugins` for the owning plugin. Use `.disabled-` directory renaming only as a fallback when a scanned provider-owned location has no known config or native disable mechanism. Never use `.disabled-` renaming to toggle Global Skills, because Global Skill directories are Shared Skill Content that Codex or Claude may reference through symlinks. When a fallback rename is used, target the actual directory from `skill.path`, never `skill.name`, because the `SKILL.md` `name` field can differ from the directory name.

### Edit, Delete, and Update
### Delete And Update

All skills can be edited (`e` opens `$EDITOR`), opened in the system file manager (`o` — uses `open` on macOS, `xdg-open` on Linux), and deleted (`d` with confirmation). There is no `readonly` flag — every skill is fully manageable.
Skills can be opened in the system file manager (`o` — uses `open` on macOS, `xdg-open` on Linux). Skillpack v1 does not edit or create skills.

**Delete routing** depends on source type:
- `skillssh`: delegates to `npx skills remove <name> -g -y` (skills CLI manages its own lock)
- `github`: removes the skill directory + removes the entry from `skillpack.lock`
- `local` / other: delegates to the provider's `uninstall` or directly removes the directory
Delete is available only for skills.sh-managed Global Skills and delegates to `npx skills remove <name> -g -y` so the skills CLI manages its own lock state.

**Update** is available only for `skillssh` and `github` sources. In the detail view, press `u` to first check for updates, then `u` again to apply. Update routing:
**Update** is available only for `skillssh` sources. In the detail view, press `u` to first check for updates, then `u` again to apply. The Updates view performs manual bulk checks. Update routing:
- `skillssh`: delegates to `npx skills update <name> -g -y`
- `github`: uninstalls then re-installs via `installFromSource`

Skills with `local` or other source types show no update UI.
Skills with `source.type === 'local'` are unmanaged on-disk skills: Skillpack found them in a provider/project directory but did not match them to skills.sh metadata. They are not necessarily created by Skillpack. They show no update or remove UI.

### TUI Alternate Screen Buffer

Expand All @@ -98,29 +109,23 @@ The TUI runs in the terminal's alternate screen buffer (like lazygit, vim). The

The `skills` CLI (`skills.sh`) installs skill files to `~/.agents/skills/` and creates symlinks in each agent directory (e.g. `~/.claude/skills/foo → ../../.agents/skills/foo`). Providers resolve symlinks via `realpath()` during scan and store the result in `skill.resolvedPath`. The `DuplicateDetector` uses resolved paths to avoid false duplicates — two skills pointing to the same real path are **not** duplicates. The detail view shows symlinks with `→` notation on the path line.

### Two Lock Systems

Skillpack reads from two separate lock systems on startup:

1. **`~/.agents/.skill-lock.json`** (skills.sh, read-only) — maintained by the `skills` CLI. Contains `source`, `sourceUrl`, `skillFolderHash`, `installedAt`, `updatedAt` per skill. Read by `SkillsLockReader` in `skills-lock.ts`. Used to hydrate `source.type = 'skillssh'` for skills whose resolved path lives under `~/.agents/skills/`.
### skills.sh Lock Metadata

2. **`~/.config/skillpack/skillpack.lock`** (skillpack, read-write) — maintained by `LockfileManager`. Only stores GitHub-installed skills (`source: 'github'`). Contains `repo`, `commit`, `ref`, `identifier`, `installedAt`. Stale entries (skills no longer on disk) and leftover `skillssh` entries are pruned on each scan.
Skillpack reads **`~/.agents/.skill-lock.json`** (skills.sh, read-only) on startup. The file is maintained by the `skills` CLI and contains `source`, `sourceUrl`, `skillFolderHash`, `installedAt`, and `updatedAt` per skill. `SkillsLockReader` in `skills-lock.ts` hydrates `source.type = 'skillssh'` for skills whose resolved path lives under `~/.agents/skills/`.

During `scanAll()`, hydration happens in two passes:
- **Pass 1**: Skills under `~/.agents/skills/` are hydrated from `.skill-lock.json` (skillssh source info)
- **Pass 2**: Remaining skills are hydrated from `skillpack.lock` (GitHub source info)
All other discovered skills use `source.type = 'local'`, meaning unmanaged on-disk provenance.

### skills.sh Install Identifiers

The `skills find` output uses `owner/repo@skillName` format (e.g. `onmax/nuxt-skills@pnpm`), but `skills add` expects `owner/repo` with an optional `--skill` flag. The `SkillsShSource` splits the identifier via `parseSkillsShIdentifier()` and constructs the correct command: `npx skills add owner/repo -g -y --skill skillName`.

### Install Flow

Install is global-only (to `~/.agents/skills/`). The install view has 3 steps: source → query → results. On result select, `installFromSource()` is called with `providerId = 'global'`. For skillssh, the skills CLI handles placement; for GitHub, skills are sparse-cloned and copied. GitHub installs record `commit`/`ref`/`repo` to `skillpack.lock` for update tracking.
Install is global-only (to `~/.agents/skills/`). The install view has 3 steps: source → query → results. On result select, `installFromSource()` is called with `providerId = 'global'`. The skills CLI handles placement. GitHub installs are not supported in v1.

### Project Skills (Read-Only)

Project-level skills are scanned from `projectSkillsDirs` (configured in `config.ts`, defaults: `.codex/skills`, `.cursor/skills-cursor`, `.claude/skills`, `.agents/skills`) relative to `cwd`. They appear with `scope: 'project'` and `provider: 'project'` in the TUI's "Project" tab. Project skills are read-only in v1 — no install, update, or lockfile management. Same-name project skills take priority over global skills during scan.
Project-level skills are scanned from `projectSkillsDirs` (configured in `config.ts`, defaults: `.codex/skills`, `.claude/skills`, `.agents/skills`) relative to `cwd`. They appear with `scope: 'project'` and `provider: 'project'` in the TUI's "Project" tab. Project skills are read-only in v1 — no install, update, toggle, or lockfile management.

### State After Mutations

Expand All @@ -139,4 +144,4 @@ After any mutation (toggle, edit, delete, update), `refresh()` must be called. T
- **Import extensions**: Must use `.js` in imports (`'./foo.js'`), not `.ts` — Node16 module resolution requires it
- **Async in `useInput`**: Fire-and-forget promises must have `.catch()` to avoid unhandled rejections crashing Ink
- **`.pnpm-store/`**: Never commit — it's in `.gitignore`
- **ClaudeProvider custom scan**: `ClaudeProvider` overrides `scan()` with its own `scanFlat()` / `scanDeep()` — changes to `BaseProvider.scan()` don't apply to Claude skills. Any scan-level feature (symlink resolution, metadata enrichment) must also be added to both Claude scan methods.
- **ClaudeProvider custom scan**: `ClaudeProvider` overrides `scan()` with its own `scanFlat()` / `scanDeep()` — changes to `BaseProvider.scan()` don't apply to Claude skills. Any scan-level feature (symlink resolution, metadata enrichment, provider-native availability) must also be added to both Claude scan methods.
73 changes: 73 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Skillpack

Skillpack is a unified management console for reusable agent skills across agent platforms and shared skill locations.

## Language

**Skill**:
A reusable instruction package that an agent can load and apply during work.
_Avoid_: Prompt, plugin, rule

**Skill Provider**:
An agent platform or shared skill location whose own rules determine where skills live and how they are loaded.
_Avoid_: Skill Library, projection target

**Provider-Native State**:
The skill inventory and availability state as represented by a Skill Provider's own files, metadata, and conventions.
_Avoid_: Skillpack state, canonical state

**Skill Availability**:
Whether a discovered skill is currently loadable by a specific Skill Provider according to that provider's own configuration, metadata, and loading rules.
_Avoid_: Directory exists, installed state

**Install Source**:
A place Skillpack can search or fetch skills from before placing them into a Skill Provider.
_Avoid_: Skill Provider, registry

**Global Skill**:
A skill in the shared global skills location managed through the skills.sh ecosystem.
_Avoid_: Universal skill, Skill Library entry

**Shared Skill Content**:
A skill directory that may be referenced by more than one Skill Provider. Availability for one provider must not be expressed by moving or renaming shared content.
_Avoid_: Provider toggle target, per-agent state

**Project Skill**:
A skill stored inside a project repository and maintained by that repository's authors through git.
_Avoid_: Global Skill, managed skill

**Plugin-Owned Skill**:
A skill distributed as part of a provider plugin. Its availability may depend on the owning plugin's access state as well as any provider-specific per-skill state.
_Avoid_: Provider-local skill, independent toggle target

**skills.sh**:
The external skill ecosystem and CLI used for installing, updating, and removing Global Skills.
_Avoid_: GitHub install source, package manager

**Unmanaged On-Disk Skill**:
A discovered skill whose source is not identified from skills.sh metadata. These skills may live in Codex, Claude, Global, or Project skill directories, but Skillpack treats their content as provider-owned or repository-owned.
_Avoid_: Installed skill, Skillpack-created skill

**Skill Inventory**:
The cross-provider view of discovered skills, their provenance, availability, and health.
_Avoid_: Skill editor, authoring workspace

**Scan Root**:
A directory Skillpack inspects to discover provider, shared global, or project skills.
_Avoid_: Skill, provider, install source

**Health Signal**:
A deterministic inventory finding that helps the user understand a skill's provider coverage, provenance, or loadability.
_Avoid_: Security score, quality rating

**Skill Group**:
The inventory row that collects provider-specific instances believed to represent the same skill.
_Avoid_: Duplicate, provider row

**Skill Identity**:
The evidence Skillpack uses to decide which provider-specific instances belong to the same Skill Group.
_Avoid_: Display name, directory name

**Disable Strategy**:
The provider-specific mechanism Skillpack uses to make a skill unavailable to an agent.
_Avoid_: Universal toggle, hidden directory rule
Loading
Loading