# Skillctl Usage

> How to drive the `skillctl` CLI non-interactively. Load PROACTIVELY when the user asks to install, push, or contribute Claude skills, or mentions a "skills library" / "skills repo". Covers every command's flag surface, exit codes, and end-to-end recipes so an agent can run `skillctl` without a TTY.

- **Type:** Skill
- **Install:** `agentstack add skill-umanio-agency-skillctl-skillctl-usage`
- **Verified:** Pending review
- **Seller:** [umanio-agency](https://agentstack.voostack.com/s/umanio-agency)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [umanio-agency](https://github.com/umanio-agency)
- **Source:** https://github.com/umanio-agency/skillctl/tree/main/.claude/skills/skillctl-usage

## Install

```sh
agentstack add skill-umanio-agency-skillctl-skillctl-usage
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# skillctl-usage

`skillctl` is a Rust CLI that manages a personal Claude skills library across projects. This skill is the agent-facing reference: it documents how to drive every command **without prompts**, so any agent (Claude Code or otherwise) can use it as a tool.

> If you're a human reading this, the same flags work in interactive mode — they pre-fill choices and skip the relevant prompts.

## How non-interactive mode is selected

`skillctl` auto-detects whether stdin and stdout are TTYs. When called from a script, agent, or pipe, it switches to non-interactive mode automatically. You can also force it explicitly with the global `--no-interaction` flag, or with `--json` (which implies it).

In non-interactive mode, **every decision must come from a flag.** If a required input is missing, the command exits with a clear error rather than silently falling back to a prompt.

## Structured output: `--json`

The global `--json` flag suppresses the human-readable cliclack output and emits a single JSON object on stdout at the end of the command. `--json` implies `--no-interaction`. Errors continue to go to stderr.

Per-command top-level shape:

```jsonc
// init
{"command":"init","library":{"url":"…","cache_path":"…"}}

// list
{"command":"list","library":"…","skills":[{"name":"…","path":"…","description":"…|null","tags":["…"]}]}

// add
{"command":"add","destination":"…|null","results":[{"name":"…","status":"installed|skipped|aborted","…":"…"}],"summary":{"installed":N,"skipped":N,"aborted":N}}

// push
{"command":"push","results":[{"name":"…","status":"pushed|forked|promoted|pr_opened|skipped","operation":"update|fork|pr","pr_url":"…","branch":"…","new_name":"…","library":"…","…":"…"}],"commit":{"sha":"…","message":"…"}|null,"summary":{"pushed":N,"forked":N,"promoted":N,"pr_opened":N,"skipped":N}}

// pull
{"command":"pull","results":[{"name":"…","status":"pulled|skipped","fork_local":"…|null","fork_local_path":"…","source_sha":"…"}],"summary":{"pulled":N,"forked_locally":N,"skipped":N}}

// detect
{"command":"detect","target":"…|null","results":[{"name":"…","status":"added|skipped","library_path":"…","local_path":"…","source_sha":"…"}],"commit":{"sha":"…","message":"…"}|null,"summary":{"added":N,"skipped":N}}

// remove
{"command":"remove","results":[{"name":"…","status":"removed|failed","path":"…","removed_folder":true|false,"removed_entry":true|false,"reason":"…"}],"summary":{"removed":N,"failed":N}}
```

Stable rules:
- The `command` field always matches the subcommand name.
- `results[]` only contains skills that were *acted on* (installed, pushed, forked, pulled, added, or explicitly skipped). Skills that were silently no-ops (e.g. unchanged on `push`) are not in `results[]`. To enumerate everything, use `skillctl list --json`.
- `summary` totals exactly equal the count of corresponding `status` values in `results[]`.
- `commit` is `null` when no commit was made (nothing to apply, or library-only-read commands like `pull`/`list`).

## One-time setup: link a library

```sh
skillctl init https://github.com//
```

- Clones the library repo into a platform-appropriate cache.
- Persists the URL in a global config file as the **default library** (named `personal`, access `write`).
- Re-running `init` against the same URL refreshes the cache; against a different URL re-points the default library.
- Any git host works — GitHub, GitLab, or self-hosted — over HTTPS or SSH (e.g. `git@host:owner/repo.git`). Cleartext `http://` is refused.

## Multiple libraries

skillctl can track more than one library. A **library** carries an access level — `read` (consume only), `write` (direct commit), or `pr` (branch + PR/MR) — and exactly one is the **default**. The personal library from `init` is just the default.

```sh
skillctl library add   [--access read|write|pr] [--default]
skillctl library list
skillctl library remove 
skillctl library set-default 
```

- `library add` clones the repo immediately (fail-fast on a bad URL/credentials). Added libraries default to `--access read` so you can't push to them by accident. The name `all` is reserved.
- The default library is what every command acts on when you don't say otherwise. `--from ` (on `list`/`add`) reads from another library; non-default reads are treated as **untrusted third-party content** (see the audit notes below).
- `pull` and `push` both **follow each skill's provenance** — a skill installed from any configured library is refreshed from / written back to that library (a run may touch several). A skill whose recorded provenance is no longer configured is listed and skipped (run `skillctl library add` to restore it).
- `push` to a `write`-access library commits directly; to a `pr`-access library it pushes a `skillctl/` branch and opens a PR (`gh`) or MR (`glab`), returning the URL. A skill from a `read`-access source can't be written back — **promote it** into a writable library with `push --to ` (see the push section). Opening a PR/MR uses your existing `gh`/`glab` auth — no token is stored; unsupported hosts get a "push done, open it manually" message.
- Each repository may be configured at most once: `skillctl library add` refuses a URL that resolves to an already-configured repo (same repo under two access levels would make a skill's write target depend on config order).
- **Interactive only:** running `skillctl add` in a terminal with more than one library configured (or `skillctl add --from all`) opens a picker with a **tab per library** (←/→ to switch, opens on the default); selections accumulate across tabs into one install. Agents/non-interactive runs use the flags above instead.

## Commands

### `skillctl list` — read-only

```sh
skillctl list
skillctl list --from           # list a specific library
skillctl list --from all             # list every configured library
skillctl list --tag  [--tag  …] [--all-tags]
```

Refreshes the library cache (best-effort `git fetch`) and prints every skill with its name, any frontmatter tags in `[…]`, and a one-line description.

| Flag | Purpose |
|---|---|
| `--from ` | List a named library instead of the default. `--from all` spans every configured library, grouped by library (each section shows the library name, access, and URL). |
| `--tag ` | Filter to skills carrying this tag. Repeatable; default semantics is union (any of the given tags). |
| `--all-tags` | Switch to intersection (skill must carry every requested tag). Requires `--tag`. |

Under `--json`, a single-library list emits `{ "command": "list", "library": , "skills": [...] }`; `--from all` emits `{ "command": "list", "from": "all", "libraries": [ { "name", "url", "access", "default", "skills": [...] } ] }`.

### `skillctl add` — install skills from the library into a project

```sh
skillctl add --skill  [--skill  …] --dest 
skillctl add --all --dest 
skillctl add --tag  [--tag  …] [--all-tags] --dest 
skillctl add --from  --skill  --dest    # install from one named library
skillctl add --from all --tag  --dest          # install matching skills from every library
```

| Flag | Purpose | Required in non-interactive |
|---|---|---|
| `--from ` | Install from a named library instead of the default. **A git URL or `github:owner/repo` / `gitlab:owner/repo` shorthand** installs ad-hoc from a remote source that isn't a configured library (see below). `--from all` installs matching skills from **every** configured library in one run (non-interactive: requires a selection — `--all`/`--skill`/`--tag`). Installing from any non-default source forces the content audit on. | No |
| `--save-as ` | Only with an ad-hoc `--from `: also register the source as a `read`-access library under this name, so `skillctl pull` can track it later. Ignored when `--from` names an already-configured library. | No |
| `--skill ` | Install a specific skill (repeatable). Mutually exclusive with `--all` and `--tag`. | Yes, unless `--all` or `--tag` |
| `--all` | Install every skill in the library. Mutually exclusive with `--skill` and `--tag`. | Yes, unless `--skill` or `--tag` |
| `--tag ` | Install every skill carrying this tag (repeatable). Default semantics is union (any of the given tags). Mutually exclusive with `--skill` and `--all`. | Yes, unless `--skill` or `--all` |
| `--all-tags` | Switch tag matching from union to intersection (skill must carry every requested tag). Requires `--tag`. | No |
| `--dest ` | Project-relative destination folder (e.g. `.claude/skills`). The folder is created if missing. | **Yes** |
| `--on-conflict ` | Strategy when a destination skill folder already exists. | Yes if any conflict is encountered |
| `--no-audit` | Skip the content security audit of skills before installing. | No |
| `--fail-on ` | Refuse the **whole batch** (install nothing, exit 5) if any selected skill's content audit reaches this severity. Without it the audit is warn-only. | No |

Before anything is copied, `add` runs a content security audit (see `skillctl audit`) on each selected skill. By default it is **warn-only** (findings are logged, the install proceeds); under `--json` each installed skill's result carries an `"audit_verdict"` field (`safe`/`caution`/`warning`/`dangerous`) so a non-interactive caller still sees the signal. Pass `--fail-on ` to block, or `--no-audit` to skip the scan entirely. The same gate covers `pull` (incoming library content) and `detect` (local content before publishing) — see their sections.

**Interactive triage (TTY only).** In an interactive terminal on the warn-only path (no `--fail-on`), if any skill is flagged (verdict ≥ `warning`) `add`/`pull`/`detect` present a **batch-triage menu** before applying: *Decide for each* (walk each flagged skill — include / skip / view findings), *Proceed with all*, or *Cancel everything* (nothing is applied, exits 0). Non-flagged skills always proceed. This never triggers non-interactively (agents/`--json` use `--fail-on`/`--no-audit` instead), so it does not affect scripted runs.

When installing from a **non-default library** (`--from ` where `` isn't the default, or `--from all` while any non-default library is configured), the content is untrusted third-party material, so the audit is **mandatory**: `--no-audit` is refused (exit 2). It is still warn-only unless you add `--fail-on`. Installs from the default library are unaffected.

**Ad-hoc remote install** (`--from `): `skillctl add --from github:owner/repo --skill foo --dest .claude/skills` clones the repo into the cache, audits its content (**mandatory** — `--no-audit` refused), and installs the selected skills with their remote-URL provenance. By default the source stays **ephemeral** — recorded in `.skills.toml` by URL but not added to `config.toml`, so `pull`/`push` skip it (it's a one-shot install). To keep tracking it, pass `--save-as ` (or accept the interactive "keep as a library?" offer) and it's registered as a `read` library. If the URL already matches a configured library, `--from ` simply installs from that library. Accepts the `github:`/`gitlab:` shorthand and full `https://`/`git@`/`ssh://` URLs; installs from the default branch HEAD.

With `--from all`, the default library is installed first and keeps the skill's bare name; if another library offers a skill whose name (or destination folder) is already taken, that install is suffixed `-` (e.g. `deploy` from `personal` + `deploy-team` from `team`), so both land with distinct names, folders, and provenance. The JSON `results[]` entries carry a `library` field naming the source.

Each installed skill is recorded in `.skills.toml` at the project root with the source path inside the library, the library commit SHA at install time, the local destination, an RFC3339 timestamp, and the provenance (`library` name + `library_url`) it was installed from.

Tags are read from the `SKILL.md` frontmatter. Both inline and block forms work:

```yaml
---
name: claude-api
description: Build and tune Claude API apps with prompt caching.
tags: [api, claude, caching]
---
```

```yaml
---
name: claude-api
tags:
  - api
  - claude
  - caching
---
```

A bare scalar `tags: foo` is accepted and treated as a single-tag list. Tag flags on `push` and `pull` read tags from each skill's **local** SKILL.md (the user's current view), so retagging locally takes effect on the next run without needing to push or pull first.

`description:` accepts both a single-line value and YAML block scalars:

```yaml
description: |
  Multi-line literal description.
  Newlines are preserved.

description: >
  Multi-line folded description that
  joins lines with spaces, useful for
  one long sentence wrapped in source.
```

### `skillctl push` — propagate local edits back to the library

```sh
skillctl push --skill  [--skill  …]
skillctl push --all
skillctl push --tag  [--tag  …] [--all-tags]
```

| Flag | Purpose |
|---|---|
| `--to ` | **Promotion mode.** Publish the selected skills into this writable library (rewriting their provenance) instead of pushing each back to its own. Use it to contribute a skill installed from a `read`-only source into your own/team library. On a path collision in the target, `--on-divergence` applies. |
| `--skill ` | Push only specific skills by name (repeatable). Mutually exclusive with `--all`/`--tag`. |
| `--all` | Push every skill that has pushable changes. Mutually exclusive with `--skill`/`--tag`. |
| `--tag ` | Push every pushable skill whose **local** SKILL.md carries this tag. Repeatable; default semantics is union (any of). Mutually exclusive with `--skill`/`--all`. |
| `--all-tags` | Switch tag matching to intersection (skill must carry every requested tag). Requires `--tag`. |
| `--on-divergence ` | Strategy for divergent (and library-missing) skills. Default when omitted: skip with a warning. |
| `--fork-suffix ` | Required when `--on-divergence fork` is used non-interactively. New name = `-`. |
| `--message ` | Override the auto-generated commit message. For a `pr`-access library it is also the PR/MR description. |
| `--pr-title ` | Title for the PR/MR opened against a `pr`-access library (default: auto-generated). Ignored for `write` libraries. |
| `--yes` | Skip the interactive PR/MR confirmation (open it without prompting). Always implied in non-interactive mode. |
| `--propagate` | After the push succeeds, fan each pushed update out to every **other** project on disk that installed it from the same library (same engine as `skillctl propagate`). Mutually exclusive with `--to`. |
| `--root ` | Directory to scan for install sites when `--propagate` is set (repeatable). Requires `--propagate`; falls back to `[propagate] roots` in `config.toml` when omitted. |

For each pushable skill, `skillctl push` runs a content diff (via git blob hashes) and applies the chosen strategy. `push` **follows provenance**: each skill is written back to the library it was installed from.
- For a `write` library: commits and pushes to its default branch, **one commit per library** (a run touching several makes several commits); each pushed `.skills.toml` entry's `source_sha` is rewritten to that library's new HEAD. In `--json`, `commit` is the single commit when exactly one `write` library was pushed, otherwise `null` (each result carries its `source_sha`).
- For a `pr` library: pushes a `skillctl/` branch and opens a PR (`gh`) / MR (`glab`); the result carries `"status":"pr_opened"`, `"pr_url"`, and `"branch"`, and the URL is shown in the outro. `.skills.toml` is **not** changed (the skill isn't merged yet). Interactive runs show an editable title + confirm; `--yes`/non-interactive open it directly.

Skills from `read` libraries, and skills whose provenance is no longer configured, are listed and skipped (see the access notes above).

**Promotion** (`push --to `): publishes the selected skills' local content into `` (regardless of where they came from) and rewrites their `.skills.toml` provenance to it — the way to contribute a skill installed from a read-only source. Each skill lands a

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [umanio-agency](https://github.com/umanio-agency)
- **Source:** [umanio-agency/skillctl](https://github.com/umanio-agency/skillctl)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-umanio-agency-skillctl-skillctl-usage
- Seller: https://agentstack.voostack.com/s/umanio-agency
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
