https://github.com/ctxr-dev/kit
https://github.com/ctxr-dev/kit
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/ctxr-dev/kit
- Owner: ctxr-dev
- License: mit
- Created: 2026-04-13T13:40:52.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-21T00:22:38.000Z (about 1 month ago)
- Last Synced: 2026-05-21T05:53:49.371Z (about 1 month ago)
- Language: JavaScript
- Size: 305 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# @ctxr/kit
[](https://www.npmjs.com/package/@ctxr/kit)
[](LICENSE)
[](https://agentskills.io)
Universal CLI for [Agent Skills](https://agentskills.io) artifacts: install,
validate, update, and scaffold **skills, agents, commands, rules,
output-styles, and teams** for Claude Code, OpenAI Codex CLI, and any other
harness that follows the open Agent Skills standard. The canonical install
location is `.agents///` (project) and `~/.agents///`
(user); kit auto-creates discovery-mirror symlinks at `.claude//`
and `~/.codex//` so harnesses that don't read `.agents/` natively
still find the artefact. The `package.json` `files` field is the single
source of truth for what each package ships, and the manifest filename
`.ctxr-manifest.json` is written per install root. Run the CLI via
`npx @ctxr/kit`; no global install required.
## Quick start
```bash
npx @ctxr/kit install @ctxr/skill-code-review # add the code-review skill
npx @ctxr/kit list # see what's installed
```
That's the whole loop. Run `npx @ctxr/kit --help` for the full command set.
## Prerequisites
- **Node.js ≥ 18.0.0** (uses ESM, `node:test`, and the modern `node:fs` API)
- A project directory where you want artifacts installed. Project-scope
installs land canonically under `.agents//`; user-scope installs
(`--user`) land canonically under `~/.agents//`. Discovery-mirror
symlinks at `.claude//` (and `~/.codex//` at user scope)
are created automatically so Claude Code and Codex CLI both find every
installed artefact without extra configuration.
## Install
Kit is run exclusively via `npx` — no global install required.
```bash
npx @ctxr/kit
```
Every example below uses the `npx @ctxr/kit` form. If you prefer a short
alias, add one to your shell (`alias kit='npx @ctxr/kit'`) — kit itself
never asks you to install it globally.
## Interactive mode (default)
`kit` is **interactive by default**. In a terminal, every command that has
a choice to make prompts you with an arrow-key menu or a short question:
- `install` shows a destination menu with the canonical candidate
locations (`.agents//`, `~/.agents//`, or a custom path
you type) and pre-highlights the one kit would auto-pick.
- For any artifact you're installing that's already installed at a
*different* location than you just picked, `install` asks per-item
whether to **keep** it there (update in place) or **move** it to the
chosen destination.
- `update` pre-flights the list — if any identifier you named isn't
installed yet, it prints the missing list and exits so you don't
silently update "everything except those".
- `remove` that finds an artifact in multiple locations asks which one to
remove. Missing identifiers are soft-skipped with a one-line note.
- `init` runs a 9-question wizard (type, name, author, description,
license, target, overwrite confirmation, git init, npm install) with
smart defaults drawn from `git config` and the current directory name.
### Non-interactive / scripted use
Non-interactive mode kicks in automatically in three cases, no flag
required:
1. **`CI=true`** environment variable is set (GitHub Actions, GitLab CI,
CircleCI, Travis, Buildkite, and most other CI runners set this).
2. **stdin is not a TTY** (e.g. `kit install X < /dev/null`, or kit
running under `spawn()` with piped stdio).
3. **Explicit `--yes` / `-y`** flag on the command line.
In non-interactive mode every prompt resolves to its declared default, and
`install` never destructively moves an existing install — it updates in
place at whatever location the artifact is currently at. This keeps
automation stable: pipelines that run `npx @ctxr/kit install X` will
always land the artifact in a predictable place.
```bash
# All equivalent — all three trigger silent, no-prompt behavior:
npx @ctxr/kit install @ctxr/skill-code-review --yes
CI=true npx @ctxr/kit install @ctxr/skill-code-review
npx @ctxr/kit install @ctxr/skill-code-review < /dev/null
```
### Forcing interactive mode in CI
If you need prompts even under `CI=true` (rare, but useful in dev
containers), pass `-i` / `--interactive`. That flag overrides all three
auto-detection triggers.
## Artifact types
`kit` understands every artifact type the Agent Skills standard recognises,
plus a `team` meta-type that bundles several of them into a single
installable package.
| Type | Canonical install path | `ctxr.target` | Typical payload |
|----------------|----------------------------------|-------------------|--------------------------|
| `skill` | `.agents/skills//` | `folder` | `SKILL.md` + assets |
| `agent` | `.agents/agents/.md` | `file` or `folder`| Single `.md` (or bundle) |
| `command` | `.agents/commands/.md` | `file` | Single `.md` |
| `rule` | `.agents/rules/.md` | `file` | Single `.md` |
| `output-style` | `.agents/output-styles/.md`| `file` | Single `.md` |
| `team` | (cascades to `ctxr.includes`) | n/a | No payload |
Each canonical install also gets a discovery-mirror symlink at
`.claude//` (project) and `~/.codex//` (user
scope) so Claude Code and Codex CLI both auto-discover the artefact;
see [Install locations](#install-locations) below.
## Per-package schema
Every artifact package declares a `ctxr` block in its `package.json`. Only
`type` and `target` (or `includes` for teams) are required — kit reads
nothing else. The npm-native `files` field is the **single source of truth**
for what ships in the package.
```json
{
"name": "@ctxr/skill-code-review",
"version": "1.0.5",
"files": [
"SKILL.md",
"code-reviewer.md",
"reviewers",
"overlays",
"README.md",
"LICENSE"
],
"ctxr": {
"type": "skill",
"target": "folder"
}
}
```
```json
{
"name": "@ctxr/team-full-stack",
"files": ["README.md"],
"ctxr": {
"type": "team",
"includes": [
"@ctxr/skill-code-review",
"@ctxr/agent-researcher",
"@ctxr/rule-typescript-strict"
]
}
}
```
- **`ctxr.type`** — one of `skill | agent | command | rule | output-style | team`. Picks the destination directory.
- **`ctxr.target`** — `"folder"` (wrap full payload in a folder) or `"file"` (copy the single payload file flat). Required for every non-team type.
- **`ctxr.includes`** — required only for teams. Array of package specs to install when this team is installed.
`kit` never introduces a second copy list. There is no `ctxr.copy`, no
`ctxr.entry`, no prefix-inferred type. If `npm pack` would ship it, `kit`
installs it; if not, it doesn't.
## Commands
### `npx @ctxr/kit install [...] [options]`
Install one or more artifacts in a single command. Sources can be mixed —
npm packages, GitHub shorthand, and local paths all work side by side.
```bash
# single artifact
npx @ctxr/kit install @ctxr/skill-code-review
# mixed batch — different types, different sources
npx @ctxr/kit install \
@ctxr/skill-code-review \
@ctxr/agent-researcher \
@ctxr/rule-typescript-strict \
github:ctxr-dev/output-style-teaching
# team meta-package — cascades to every member
npx @ctxr/kit install @ctxr/team-full-stack
# user-global instead of project-local
npx @ctxr/kit install @ctxr/skill-code-review --user
# explicit destination
npx @ctxr/kit install @ctxr/skill-code-review --dir .agents/skills
# local path (must start with ./, /, or ~/)
npx @ctxr/kit install ./path/to/local-skill
```
**Batch behavior:** if one package in the batch fails (broken `ctxr` block,
`target: "file"` payload that resolves to ≠1 file, network error on a single
fetch), that package is reported and skipped — the rest of the batch
proceeds and `kit` exits non-zero only if anything failed. No all-or-nothing
abort.
#### Install locations
| Location | Role |
|---------------------------|-------------------------------------------------------|
| `.agents//` | Project-scope canonical (real files live here) |
| `.claude//` | Project-scope discovery mirror (symlink, auto) |
| `~/.agents//` | User-global canonical (real files live here) |
| `~/.claude//` | User-global discovery mirror for Claude Code (symlink)|
| `~/.codex//` | User-global discovery mirror for Codex CLI (symlink) |
| Custom path | Via `--dir ` (mirrors are skipped) |
For project-scope installs `kit` also upserts a row in `AGENTS.md` at
the project root, with stable `` /
`` markers. Anything you author outside the
markers is preserved verbatim across re-installs and removes.
To disable kit's `AGENTS.md` emitter entirely (no creates, no upserts,
no removes) set `CTXR_NO_AGENTS_MD=1` in the environment. Useful when
`AGENTS.md` is hand-authored, kept in `.gitignore`, or otherwise managed
outside kit.
#### Migration of legacy `.claude///` installs
When `kit install` runs and detects a real (non-symlink) directory at
the legacy `.claude///` path with a recorded manifest row,
it moves the directory to `.agents///`, replaces the
original with a symlink, and migrates the manifest row. The same
applies to user-scope installs at `~/.claude//`. Migration is
idempotent and skipped when `--dir` is set (a deliberate custom layout
is left alone). `kit update` does NOT auto-migrate; it preserves
whatever layout you originally chose so a routine update never
surprises you with a relocation.
Auto-detect: `kit` always installs canonically to `.agents//`
and creates the discovery mirrors automatically. If a legacy real
`.claude///` install is found, the migration step above
moves it to the canonical path before installing.
### `npx @ctxr/kit update [name]`
Re-install one or all artifacts in place using the source recorded in the
manifest. Searches every project- and user-scope manifest, so you don't
need to remember where each artifact lives.
```bash
npx @ctxr/kit update # update everything
npx @ctxr/kit update ctxr-skill-code-review # update one
```
Team updates cascade to every member.
### `npx @ctxr/kit remove [--keep-members]`
Remove an installed artifact (or team). For teams, every member listed in
the manifest is removed too unless `--keep-members` is passed.
```bash
npx @ctxr/kit remove ctxr-skill-code-review
npx @ctxr/kit remove ctxr-team-full-stack --keep-members
```
### `npx @ctxr/kit list [path]`
List installed artifacts from every discovered location, grouped by type.
```bash
npx @ctxr/kit list
npx @ctxr/kit list ./other-project
```
### `npx @ctxr/kit info `
Show details about an installed or remote artifact: type, target layout,
source, version, file count, install paths.
```bash
npx @ctxr/kit info @ctxr/skill-code-review
npx @ctxr/kit info ctxr-agent-researcher
```
### `npx @ctxr/kit validate [path]`
Validate an artifact package's structure ahead of publishing. Dispatches
to a per-type validator: skill validation (frontmatter, broken-link
checker, file budget) is the heaviest; the rest are thin frontmatter
sanity checks plus the universal `target: "file"` ⇒ exactly-one-`.md`-file
rule that the installer enforces.
```bash
npx @ctxr/kit validate # validate package in current dir
npx @ctxr/kit validate ./my-skill # validate at a path
```
### `npx @ctxr/kit init [--type ] [name]`
Scaffold a new artifact package from a template. Defaults to
`--type skill` because that's the most common authoring case.
```bash
npx @ctxr/kit init my-skill # default --type skill
npx @ctxr/kit init --type agent my-agent # scaffold an agent
npx @ctxr/kit init -t command deploy # short-form flag
npx @ctxr/kit init --type team team-full-stack # scaffold a team meta-package
```
Each template ships a `package.json` with the right `ctxr` block already
filled in, plus `README.md`, `LICENSE`, and `.gitignore` (the skill
template additionally ships `.markdownlint.jsonc` and a starter
`SKILL.md`; file-target templates ship a pre-named `ctxr-{{name}}.md`).
The scaffolded result passes `npx @ctxr/kit validate` immediately — edit
the contents, then publish.
## Global options
| Flag | Effect |
|-----------------------|----------------------------------------------------------|
| `--dir ` | Operate against a specific directory |
| `--user` | Use `~/.claude//` (user-global) instead of project |
| `-i`, `--interactive` | Prompt for choices where applicable |
| `--help`, `-h` | Show help (top-level or per-command) |
| `--version`, `-v` | Print the installed version of `@ctxr/kit` |
Run `npx @ctxr/kit --help` for command-specific options.
## Releasing
Releases are PR-gated. Version bumps land on `main` through a review gate like any other change; only the tag push is automated.
### One-time setup
Enable these on the repo before your first release:
- Repository secret `NPM_TOKEN` set to an npm access token with publish rights on the `@ctxr` scope (`npm token create`, then **Settings → Secrets → Actions** → add `NPM_TOKEN`).
- **Settings → Actions → General → Workflow permissions**: enable **Allow GitHub Actions to create and approve pull requests** so `release.yml` can open its version-bump PR with `GITHUB_TOKEN`. If the checkbox is greyed out, an organization-level Actions policy is restricting it; ask an org admin to unlock the setting first.
- (Optional, recommended) GitHub-managed CodeQL default setup: **Security → Code security** → enable default setup for `javascript-typescript` and `actions`.
- (Optional) A branch ruleset on `main` requiring PR review + code scanning. The release flow works without it; gates are strictly stricter when enabled.
### Cutting a release
1. **Actions → Release → Run workflow**.
- Branch selector: `main` (the workflow refuses any other ref).
- Version bump: `patch` / `minor` / `major`.
- Click **Run workflow**.
2. The workflow bumps `package.json` on a fresh `release/v` branch and opens a PR to `main` titled `release: v`.
3. Review the PR (diff is just version fields). Approve + merge.
4. On merge, `tag-on-main.yml` fires automatically:
- Detects the version change.
- Creates and pushes the annotated `v` tag via `GITHUB_TOKEN`.
5. **Actions → Publish to npm → Run workflow** on the `v` tag. The workflow re-runs `lint / validate / test:unit / test:integration / test:e2e`, verifies the tag matches `package.json`, and publishes `@ctxr/kit` to npm.
> **Why a manual dispatch for step 5?** GitHub's built-in `GITHUB_TOKEN` cannot trigger further workflows (`on: push: tags` won't fire when a workflow pushed the tag). So the tag auto-creation stops at the tag. Publishing is one extra click. To make it fully automatic, swap the push credential in `tag-on-main.yml` for a GitHub App token or fine-grained PAT stored as a repo secret (`actions/create-github-app-token` or a `secrets.TAG_PUSH_PAT`), then the `push: tags` trigger on `publish.yml` will fire and step 5 happens by itself.
From **Run workflow** on Release to **published on npm** is one dispatch + one PR merge + one dispatch (or one dispatch + one PR merge, once a PAT/App-token is wired in).
### Troubleshooting
- **Release workflow fails with "dispatched from non-main ref"** — you selected a feature branch in the Actions UI. Re-dispatch with `main`.
- **`tag-on-main` fails with "Tag vX.Y.Z exists but points at …"** — a stale/orphan tag from a prior failed release. Delete and re-run:
```bash
git push origin --delete vX.Y.Z
```
Then merge a trivial no-op PR to `main` (or revert-and-re-merge the release PR) to retrigger `tag-on-main`. Direct pushes to `main` may be blocked by branch protection, so the PR path is the reliable retrigger.
- **`publish.yml` fails on "Verify version matches tag"** — tag and `package.json` disagree. Investigate the merge commit; this should not happen under the PR-based flow.
- **GitHub Actions is not permitted to create pull requests** — org or enterprise policy blocks the `GITHUB_TOKEN` from opening PRs. Enable **Allow GitHub Actions to create and approve pull requests** at the org level (**Settings → Actions → General → Workflow permissions**), or ask the enterprise admin to unlock the setting.
## Development
```bash
npm install
npm test # unit + integration
npm run test:unit # unit only
npm run test:integration # integration only
npm run test:e2e # multi-location + multi-type
```
## License
[MIT](LICENSE)