Complete reference for every field in the clictl tool spec format.
For a quick-start authoring guide, see spec-format.md. For transforms, composites, and code mode, see the other docs in this directory.
Spec format: YAML (for authoring) or JSON (for machine consumption). One spec per file.
A spec describes a tool that an AI agent can discover, understand, and execute through clictl. Every spec lives in the toolbox at {first-letter}/{tool-name}/{tool-name}.yaml. For example, g/github/github.yaml.
clictl supports four tool types, all using the same spec format:
| Type | Server type | Actions defined by |
|---|---|---|
| REST API | type: http (optional) |
method, url, and path directly on action |
| CLI wrapper | type: command |
run with a shell command template |
| MCP server | type: stdio or type: http |
Static list, dynamic discovery, or both |
| Skill | No server block | source block with repo and files |
The agent always sees the same thing: a list of named operations with params, descriptions, and examples.
spec: "1.0"
# --- Identity (required) ---
name: my-tool
namespace: publisher-name
description: One-line description of what the tool does
version: "1.0"
category: developer
tags: [api, example]
# --- Server (required for API/MCP/CLI, omit for skills) ---
server:
# ...
# --- Auth (optional, omit for no-auth tools) ---
auth:
# ...
# --- Instructions (optional, strongly recommended) ---
instructions: |
When to use this tool and what to watch out for.
# --- Dependencies (optional) ---
depends:
# ...
# --- Pricing (optional) ---
pricing:
# ...
# --- Privacy (optional) ---
privacy:
# ...
# --- Actions (required) ---
actions:
# ...| Field | Type | Description |
|---|---|---|
name |
string | Unique tool name in kebab-case. Must match the filename. |
description |
string | Clear one-line description. Used in search results. |
version |
string | Spec version, e.g. "1.0". Quoted to prevent YAML float parsing. |
category |
string | Primary category: developer, data, devops, geo, productivity, ai, utilities, etc. |
tags |
list | Search tags. Include the tool name, protocol, and key concepts. |
| Field | Type | Default | Description |
|---|---|---|---|
spec |
string | "1.0" |
Spec format version. |
namespace |
string | - | Publisher identity for marketplace display. Not the resolution namespace. |
instructions |
string | - | Markdown guidance for agents: when to use, when not to use, rate limits, gotchas. |
canonical |
string | - | Source URL for this spec. Used by clictl audit to check divergence. |
deprecated |
bool | false |
Whether the tool is deprecated. |
deprecated_message |
string | - | Human-readable deprecation notice. |
deprecated_by |
string | - | Name of the replacement tool. |
One block, three types. The type field is explicit.
server:
type: http
url: https://api.github.com
headers:
Accept: application/vnd.github+json
X-GitHub-Api-Version: "2022-11-28"
timeout: 15s| Field | Type | Default | Description |
|---|---|---|---|
type |
string | (required) | http |
url |
string | (required) | Base URL for all requests. |
headers |
map | - | Headers sent with every request. |
timeout |
string | 30s |
Connection timeout. Duration string (e.g. 10s, 1m). |
server:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home"]
env:
NODE_ENV: production
requires:
- name: node
check: "node --version"
url: https://nodejs.org| Field | Type | Default | Description |
|---|---|---|---|
type |
string | (required) | stdio |
command |
string | (required) | Binary to execute. |
args |
list | - | Command-line arguments. |
env |
map | - | Environment variables passed to the process. Supports ${KEY} vault references. |
requires |
list | - | System binary requirements. Each has name, check (command to verify), and url (install link). |
server:
type: command
shell: bash
requires:
- name: docker
check: "docker --version"
url: https://docs.docker.com/get-docker/| Field | Type | Default | Description |
|---|---|---|---|
type |
string | (required) | command |
shell |
string | bash |
Shell used to execute run commands. |
requires |
list | - | Same as stdio requires. |
server:
type: websocket
url: wss://stream.binance.com:9443/ws
headers:
X-Custom-Header: value
timeout: 10s| Field | Type | Default | Description |
|---|---|---|---|
type |
string | (required) | websocket |
url |
string | (required) | WebSocket URL (wss:// or ws://). |
headers |
map | - | Headers sent during the WebSocket handshake. |
timeout |
string | 10s |
Handshake timeout. |
Actions on WebSocket specs use these additional fields:
| Field | Type | Default | Description |
|---|---|---|---|
message |
string | - | JSON message to send after connecting. Supports ${param} templating. |
wait |
string | 5s |
Duration to listen for responses. |
collect |
int | 1 |
Number of messages to collect before returning. |
Template model. env names the vault keys. header/param + value describes exactly what gets sent. No implicit behavior, no magic prefixes.
auth:
env: GITHUB_TOKEN
header: Authorization
value: "Bearer ${GITHUB_TOKEN}"auth:
env: ANTHROPIC_API_KEY
header: x-api-key
value: "${ANTHROPIC_API_KEY}"auth:
env: NASA_API_KEY
param: api_key
value: "${NASA_API_KEY}"auth:
env: [DD_API_KEY, DD_APP_KEY]
headers:
DD-API-KEY: "${DD_API_KEY}"
DD-APPLICATION-KEY: "${DD_APP_KEY}"auth:
type: oauth2
env: SLACK_TOKEN
header: Authorization
value: "Bearer ${SLACK_TOKEN}"
scopes: [channels:read, chat:write]The OAuth flow is user-initiated: clictl connect <tool> opens the browser, user authorizes, token is stored in the vault. On subsequent runs, ${SLACK_TOKEN} resolves from the vault.
Omit the auth block entirely.
| Field | Type | Description |
|---|---|---|
env |
string or list | Vault key name(s) that clictl needs to resolve. |
header |
string | Single header name to set. Mutually exclusive with headers. |
headers |
map | Multiple headers to set. Mutually exclusive with header. |
param |
string | Query parameter name (for query string auth). |
value |
string | Template with ${KEY} placeholders. What you write is what gets sent. |
type |
string | oauth2 for OAuth flows. Omit for simple key-based auth. |
scopes |
list | OAuth2 scopes (only with type: oauth2). |
envlists the vault key names clictl needsclictl info <tool>shows which keys are set and which are missing- At runtime,
${KEY}invalueis replaced with the resolved secret - Resolution order: project vault, user vault, workspace vault, environment variable
- The composed header/param is sent with the request
Every action has the same agent-facing fields. The execution details differ by server type.
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | (required) | Action name in snake_case or kebab-case. |
description |
string | (required) | What this action does. One line. |
output |
string | json |
Output format: json, text, html, xml, csv. |
mutable |
bool | false |
Whether this action changes state. Only include when true. |
instructions |
string | - | Action-specific guidance for the agent. |
params |
list | - | Input parameters. See Params section. |
response |
object | - | Example output and description. See Response section. |
assert |
list | - | Response validation rules. See Asserts section. |
transform |
list | - | Output transformation pipeline. See Transforms section. |
retry |
object | - | Retry configuration. See Retry section. |
pagination |
object | - | Pagination configuration. See Pagination section. |
stream |
bool | false |
Whether to stream the response. |
stream_timeout |
string | 30s |
Idle timeout for streaming actions. |
deprecated |
bool | false |
Whether this action is deprecated. |
deprecated_by |
string | - | Name of the replacement action. |
Define method, url, and path directly on the action. Method defaults to GET. The server.url is used as a fallback if the action omits url.
actions:
- name: search_repos
description: Search repositories across all of GitHub
output: json
url: https://api.github.com
path: /search/repositories
params:
- name: q
required: true
example: "language:go stars:>100 cli"
assert:
- type: status
values: [200]
transform:
- type: json
extract: "$.items"
select: [full_name, description, language, stargazers_count]
rename: { stargazers_count: stars }POST with body params:
actions:
- name: create_message
description: Send a message to the Claude API
mutable: true
method: POST
url: https://api.anthropic.com
path: /v1/messages
params:
- name: model
required: true
example: "claude-sonnet-4-20250514"
- name: max_tokens
type: int
required: true
example: "1024"
- name: messages
type: array
required: true
assert:
- type: status
values: [200]
transform:
- type: json
extract: "$.content[0].text"Path parameters are detected from {name} syntax in the path. No in: path needed:
- name: get_repo
url: https://api.github.com
path: /repos/{owner}/{repo}
params:
- name: owner
required: true
example: "anthropics"
- name: repo
required: true
example: "claude-code"Multi-API actions can target different URLs and auth per action:
actions:
- name: list-users
url: https://api.acme.com/v2
path: /users
auth:
env: ACME_KEY
header: Authorization
value: "Bearer ${ACME_KEY}"
- name: list-invoices
url: https://billing.acme.com/v1
path: /invoices
auth:
env: BILLING_KEY
header: Authorization
value: "Bearer ${BILLING_KEY}"| Field | Type | Default | Description |
|---|---|---|---|
method |
string | GET |
HTTP method: GET, POST, PUT, PATCH, DELETE |
url |
string | server.url |
Base URL or full URL for this action |
path |
string | - | URL path appended to url (optional if full URL in url) |
headers |
map | server.headers |
Request headers (merged with server-level headers) |
auth |
object | top-level auth |
Auth config for this action (overrides top-level) |
Add a run field with a shell command template:
actions:
- name: ps
description: List running containers
output: text
run: "docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}'"
- name: logs
description: Show container logs
output: text
run: "docker logs {{name}} --tail {{lines}}"
params:
- name: name
required: true
example: "my-app"
- name: lines
type: int
default: "100"
- name: stop
description: Stop a running container
mutable: true
run: "docker stop {{name}}"
params:
- name: name
required: true
example: "my-app"Template vars {{name}} are replaced from params. No request block needed.
No execution block. The action name maps to an MCP tool call. clictl routes the call through the MCP protocol because the server block has type: stdio or is an MCP endpoint.
actions:
- name: read_file
description: Read file contents
output: text
params:
- name: path
required: true
example: "./src/main.ts"
response:
example: |
import express from 'express'
const app = express()| Field | Type | Default | Description |
|---|---|---|---|
name |
string | (required) | Parameter name. |
type |
string | string |
Type: string, int, float, bool, array, object. |
required |
bool | false |
Whether the param is required. Omit unless true. |
description |
string | - | What this param does. |
example |
string | - | Example value. Strongly recommended. |
default |
string | - | Default value if not provided. Always a string (clictl coerces to type). |
values |
list | - | Allowed values (enum). clictl validates at runtime. |
in |
string | (auto) | Where the param is sent. Usually auto-detected; see below. |
| Context | Default in |
Notes |
|---|---|---|
| GET request | query |
Omit in for query params. |
| POST/PUT/PATCH request | body |
Omit in for body params. |
{name} in path |
path |
Auto-detected. Never needs to be specified. |
| Deviation from default | Explicit | Only specify in when it differs from the default. |
params:
- name: sort
values: [stars, forks, updated, created]
default: "stars"The agent sees the allowed values. clictl validates at runtime.
Helps agents understand what they will get back. Optional but strongly recommended.
response:
description: |
Returns an array of matching repositories. Each entry includes
the full name, description, primary language, and star count.
Results are sorted by best match unless `sort` is specified.
example: |
[
{
"full_name": "anthropics/claude-code",
"description": "CLI for Claude",
"language": "TypeScript",
"stars": 25000
}
]| Field | Type | Description |
|---|---|---|
description |
string | What the response contains and how to interpret it. |
example |
string | Example output after transforms are applied, not the raw API response. |
The example shows what the agent will actually see. This is critical for agents learning the output shape.
Transforms are applied in order as a pipeline. Each step takes the output of the previous step. Every step has a type field and type-specific settings.
request -> retry on transient error -> assert -> transform -> output
Operate on structured JSON data. Multiple operations can combine in one step.
# Extract a value from nested JSON using JSONPath
- type: json
extract: "$.data.results"
# Keep only listed fields
- type: json
select: [name, email, created_at]
# Rename fields for readability
- type: json
rename:
stargazers_count: stars
html_url: url
# Combined in one step
- type: json
extract: "$.items"
select: [full_name, description, language, stargazers_count]
rename: { stargazers_count: stars }
# Add default fields to every object
- type: json
inject:
source: "github"
# Allowlist top-level keys on root object
- type: json
only: [results, total_count]
# Flatten nested arrays
- type: json
flatten: true # [[1,2],[3,4]] -> [1,2,3,4]
# Unwrap single-item arrays
- type: json
unwrap: true # [{"name":"x"}] -> {"name":"x"}
# Fill missing fields with defaults
- type: json
default:
language: "unknown"
stars: 0# Sort by field
- type: sort
field: stars
order: desc # asc or desc
# Filter items by condition
- type: filter
jq: ".stars > 100" # jq expression, true/false per item
# Deduplicate by field
- type: unique
field: name
# Group by field value
- type: group
field: language # {"Go": [...], "Rust": [...]}
# Count items
- type: count # returns integer
# Join array into string
- type: join
separator: ", " # ["a","b","c"] -> "a, b, c"
# Split string into array
- type: split
separator: "," # "a,b,c" -> ["a","b","c"]- type: truncate
max_items: 20 # Limit array length
max_length: 8000 # Limit string length (characters)# Convert HTML to markdown
- type: html_to_markdown
remove_images: true
remove_links: false
# Strip markdown to plain text
- type: markdown_to_text
# Format each item using simple template
- type: format
template: "- {full_name} ({language}, {stars} stars)"
# Go text/template for complex logic
- type: template
template: |
{{range .}}{{.full_name}} ({{if .stars}}{{.stars}} stars{{end}})
{{end}}
# Add a prefix to the output
- type: prefix
value: "Results from GitHub:\n\n"
# Reformat dates
- type: date_format
field: created_at
from: "2006-01-02T15:04:05Z" # Go time layout or "iso8601"
to: "Jan 2, 2006"# Parse XML to JSON
- type: xml_to_json
# Parse CSV to JSON array
- type: csv_to_json
headers: true # First row is headers
# Decode base64 content
- type: base64_decode
field: content # Decode a specific field, or omit for entire response# Redact sensitive fields
- type: redact
patterns:
- field: "*.email"
replace: "[redacted]"
- field: "*.api_key"
replace: "[redacted]"# Track token/cost usage from AI API responses
- type: cost
input_tokens: "$.usage.input_tokens"
output_tokens: "$.usage.output_tokens"
model: "$.model"# Pipe through another clictl tool
- type: pipe
tool: jq
action: filter
params:
filter: "[.[] | {name, stars}]"
# Pipe with simple syntax
- type: pipe
run: "jq filter --filter '[.[] | select(.stars > 100)]'"The piped tool must be listed in depends.
# jq expression
- type: jq
filter: "[.[] | {name: .full_name, stars: .stargazers_count}] | sort_by(-.stars)"
# JavaScript (sandboxed)
- type: js
script: |
function transform(data) {
return data.filter(r => r.score > 0.5);
}Use jq and js as escape hatches when declarative transforms are not enough.
Inject agent-facing guidance into the output. The prompt text is appended to the transformed data.
# Unconditional: always append guidance
- type: prompt
value: "Results are sorted by relevance. Use --sort stars for popularity."
# Conditional: only when the response matches
- type: prompt
when: "size(data) == 0"
value: "No results found. Try broadening the search or using different keywords."
# Dynamic: reference response fields
- type: prompt
when: "data.rate_limit_remaining < 100"
value: "Rate limit is low ({{rate_limit_remaining}} remaining). Space out requests."Transforms run at different points in the action lifecycle. Most run at on_output (the default). Specify on only when overriding.
on_request -> Execute action -> on_response -> on_output
| Phase | When | Input | Use case |
|---|---|---|---|
on_request |
Before the request is sent | Params, headers, body | Rename params, inject defaults |
on_response |
After response, before output transforms | Raw response | Validate, log, extract metadata |
on_output |
After response transforms (default) | Transformed data | Shape data for the agent |
transform:
# Rename agent-friendly params to API params
- type: rename_params
on: request
map:
q: query
limit: per_page
# Inject default params
- type: default_params
on: request
values:
format: json
per_page: 10
# Build a GraphQL request body
- type: template_body
on: request
template: '{"query": "{ search(q: \"{q}\") { id title } }"}'
# Shape the response (default phase, no `on` needed)
- type: json
extract: "$.data.items"
select: [name, stars]
- type: truncate
max_items: 20For MCP servers, transforms are keyed by action name in a top-level transforms block:
transforms:
read_file:
- type: truncate
max_length: 8000
list_directory:
- type: truncate
max_items: 100
"*": # Default for all actions
- type: truncate
max_length: 16000Linear pipelines process steps sequentially (A -> B -> C). For parallel, branching, and merging workflows, use DAG fields on transform steps.
Every transform step can optionally have:
| Field | Type | Default | Description |
|---|---|---|---|
id |
string | - | Name for this step's output. Required for DAG steps. |
input |
string or list | previous step | Which step(s) to read from. Use step id values. |
depends |
list | - | Steps that must complete first (ordering without data flow). |
each |
bool | false |
Iterate over input array, run step once per item. |
when |
string | - | CEL expression. Step only runs if true. |
Steps without DAG fields run in linear order (backward compatible).
Parallel enrichment:
transform:
- id: repos
type: json
extract: "$.items"
select: [full_name, description, language, stargazers_count]
- id: translated
input: repos
type: pipe
run: "deepl translate --target_lang DE"
- id: formatted
input: repos
type: format
template: "{full_name}: {stargazers_count} stars"
- type: merge
sources: [translated, formatted]
strategy: zipMerge strategies:
| Strategy | Description |
|---|---|
zip |
Combine arrays item-by-item. Both inputs must be same length. |
concat |
Concatenate arrays. |
first |
Take the first non-null input. For conditional branches. |
join |
Join objects by a shared field (on). Like a SQL JOIN. |
object |
Combine into one object: {translated: [...], formatted: [...]}. |
Validate responses before transforming. Fails fast with a clear error instead of silently transforming bad data.
assert:
# HTTP status code check
- type: status
values: [200, 201]
# JSON field checks
- type: json
exists: "$.data"
not_empty: "$.data.results"
# jq expression (returns true/false)
- type: jq
filter: ".data | length > 0"
# JavaScript (return true/false)
- type: js
script: |
function assert(response) {
return response.status === 'ok' && response.data.length > 0;
}
# Common Expression Language
- type: cel
expression: "response.status_code == 200 && size(response.body.data) > 0"
# String match on response body
- type: contains
value: "results"Assert types by server type:
| Assert type | HTTP actions | CLI actions | MCP actions |
|---|---|---|---|
status |
HTTP status code | Exit code (values: [0]) |
Not applicable |
json |
Response body | stdout (if JSON) | JSON-RPC result |
jq |
Response body | stdout | JSON-RPC result |
js |
Full response object | stdout + exit code | JSON-RPC result |
cel |
Full response object | stdout + exit code | JSON-RPC result |
contains |
Response body | stdout | JSON-RPC result |
Retry handles transient errors before assert runs. On the action, not on assert.
retry:
on: [429, 500, 502, 503] # Status codes that trigger retry
max_attempts: 3 # Total attempts (1 initial + 2 retries)
backoff: exponential # exponential, linear, fixed
delay: 1s # Initial delay (doubles each retry for exponential)| Field | Type | Default | Description |
|---|---|---|---|
on |
list | [429, 500, 502, 503] |
Status codes or error types that trigger retry. |
max_attempts |
int | 3 |
Total attempts including the first. |
backoff |
string | exponential |
Backoff strategy: exponential, linear, fixed. |
delay |
string | 1s |
Initial delay between retries. |
For CLI actions, on matches exit codes. For MCP actions, on matches JSON-RPC error codes.
Omit the retry block entirely to disable retry (fail on first error). Minimal form:
retry:
on: [429, 500]Declares how clictl auto-paginates with --all.
pagination:
type: page
param: page
per_page_param: per_page
per_page_default: 30
max_pages: 10pagination:
type: cursor
param: starting_after
cursor_path: "$.data[-1].id"
has_more_path: "$.has_more"
max_pages: 10pagination:
type: offset
param: offset
per_page_param: limit
per_page_default: 25
max_pages: 10Usage: clictl run github list_issues --all auto-paginates.
actions:
- name: tail_logs
stream: true
stream_timeout: 30s
run: "docker logs -f {{name}}"
- name: watch_events
stream: true
stream_timeout: 60s
url: https://api.example.com
path: /events/stream| Field | Type | Default | Description |
|---|---|---|---|
stream |
bool | false |
Enable streaming output. |
stream_timeout |
string | 30s |
Idle timeout. clictl stops if no new data within this window. Set to 0 to disable. |
Multi-step workflows that chain API calls. If an action has steps, it is composite. Steps form a DAG resolved via topological sort.
actions:
- name: onboard-user
description: Create a user and assign default role
url: https://api.acme.com/v2
auth:
env: ACME_KEY
header: Authorization
value: "Bearer ${ACME_KEY}"
params:
- name: email
required: true
- name: name
required: true
steps:
- id: create
method: POST
path: /users
params:
email: "${params.email}"
name: "${params.name}"
- id: assign-role
method: POST
path: /users/${steps.create.id}/roles
depends: [create]
params:
role: "member"Steps inherit url, auth, and headers from the parent action. Override any field on a step to target a different API. Steps can also delegate to other tools via tool and action fields.
| Field | Type | Default | Description |
|---|---|---|---|
id |
string | (required) | Unique step identifier |
method |
string | GET |
HTTP method |
url |
string | parent's url | Base URL |
path |
string | parent's path | URL path |
headers |
map | parent's headers | Request headers |
auth |
object | parent's auth | Auth config |
tool |
string | - | Delegate to another tool |
action |
string | - | Action on the delegated tool |
params |
map | - | Parameters (supports ${params.*} and ${steps.*} expressions) |
depends |
list | - | Step IDs that must complete first |
condition |
string | - | Skip if evaluates to false |
on_error |
string | fail |
fail, skip, or continue |
retry |
object | - | Retry config |
| Limit | Value |
|---|---|
| Max steps | 20 |
| Max dependency depth | 3 |
| Nested composites | Not supported |
Transforms can also chain tools using type: pipe:
depends:
- deepl
actions:
- name: search_and_translate
description: Search repos and translate descriptions
url: https://api.github.com
path: /search/repositories
params:
- name: q
required: true
transform:
- id: results
type: json
extract: "$.items"
select: [full_name, description]
- id: translated
input: results
type: pipe
run: "deepl translate --target_lang DE"
- type: truncate
input: translated
max_items: 5Each pipe step runs through the referenced tool's own spec and sandbox rules.
All stdio MCP servers automatically discover tools at runtime by calling tools/list on the server. No configuration needed.
Static actions in the spec serve as metadata for search indexing and documentation. At runtime, the actual tools come from the server.
# Static actions are optional metadata for search and clictl info
actions:
- name: query
description: Execute a SQL query
# Control which tools are exposed to the agent
deny:
- "drop_*"
- "truncate_*"
allow:
- "query"
- "list_*"
- "describe_*"
transforms:
"*":
- type: truncate
max_items: 100| Field | Type | Description |
|---|---|---|
actions |
list | Optional. Static action metadata for search and documentation. Not used for execution. |
allow |
list | Glob patterns. Only matching tools are exposed to the agent. |
deny |
list | Glob patterns. Matching tools are never exposed. |
How it works:
- Static actions in the spec are indexed for search and shown by
clictl info - At runtime, clictl starts the server and calls
tools/list - Spec static actions override server descriptions (spec wins for name matches)
- Server tools not in the spec pass through as-is
denypatterns filter the resultallowpatterns whitelist (if present, only matching tools pass through)- The agent sees one unified list
Skills are prompt-based agent capabilities. They have no server or actions blocks. The source block tells clictl where to fetch the skill files.
spec: "1.0"
name: pdf
namespace: anthropic
description: Extract and analyze PDF content
version: "1.0"
category: productivity
tags: [pdf, document, extract]
source:
repo: anthropics/claude-code
path: skills/pdf
ref: main
files:
- path: SKILL.md
sha256: a1b2c3d4e5f6...
- path: helpers/extract.py
sha256: f6e5d4c3b2a1...
depends:
- poppler
sandbox:
bash_allow: [uv, python3, pdftotext]
filesystem:
read: ["**/*.pdf", "**/*.md"]
write: ["**/*.md", "**/*.txt"]| Field | Type | Required | Description |
|---|---|---|---|
source.repo |
string | yes | Repository URL or GitHub short path (owner/repo). |
source.path |
string | yes | Directory path within the repo. |
source.ref |
string | no | Git ref - branch, tag, or commit (default: main). |
source.files |
list | yes | Files to fetch. Each has path and sha256 hash. |
The repo field accepts either a short GitHub path or a full URL:
| Format | Resolved to |
|---|---|
owner/repo |
https://github.com/owner/repo |
github.com/owner/repo |
https://github.com/owner/repo |
gitlab.com/owner/repo |
https://gitlab.com/owner/repo |
https://github.com/owner/repo |
Used as-is |
https://git.internal.com/org/repo |
Used as-is |
git@github.com:owner/repo.git |
Used as-is |
Short paths (no protocol prefix, single slash) default to GitHub. For GitLab, Bitbucket, or self-hosted repos, use the full URL.
Skills can declare system requirements that must be installed:
requires_system:
- name: python3
check: python3 --version
brew: python3
- name: gh
check: gh --version
url: https://cli.github.com| Field | Type | Description |
|---|---|---|
requires_system[].name |
string | Dependency name. |
requires_system[].check |
string | Shell command to verify installation. |
requires_system[].brew |
string | Homebrew formula (optional). |
requires_system[].url |
string | Install URL if brew is not available. |
Skills that depend on MCP servers:
requires_mcp:
- supabase-mcp
- filesystem| Field | Type | Description |
|---|---|---|
sandbox.bash_allow |
list | Allowed shell commands. |
sandbox.filesystem.read |
list | Glob patterns for allowed reads. |
sandbox.filesystem.write |
list | Glob patterns for allowed writes. |
Skill trust model:
- SHA256 hashes on every file. Mismatch aborts install.
- Toolbox trust: user explicitly adds toolbox sources.
- Sandbox constraints restrict runtime behavior.
- Skills from unverified publishers require
--truston first install.
Tools can depend on other tools. Any tool type can depend on any other.
depends:
- jq # CLI tool used in transform pipeline
- deepl # REST API used to translate output
- postgres-mcp # MCP server this tool needs runningDependencies are clictl tools (by name), not system binaries. System binary requirements go in server.requires.
On clictl install, clictl checks depends and warns about missing dependencies. Qualified names are supported: depends: [community/jq].
Resolution order:
- Same toolbox as the dependent
- Other installed toolboxes (config order)
- Community toolbox
Signals whether a tool has costs. No dollar amounts - they go stale immediately.
# Free tool: omit the pricing block entirely
# Paid
pricing:
model: paid
url: https://stripe.com/pricing
# Freemium (free tier available)
pricing:
model: freemium
url: https://api.openai.com/pricing
# Enterprise / contact sales
pricing:
model: contact
url: https://example.com/enterprise| Field | Type | Default | Description |
|---|---|---|---|
model |
string | free |
One of: free, freemium, paid, contact. |
url |
string | - | Where the user signs up or sees pricing. |
Optional. Primarily useful for enterprise environments where workspace admins filter tools by data handling characteristics.
# Local tool - no data leaves the machine
privacy:
local: true
# Tool may process personally identifiable data
privacy:
pii: trueOmit entirely for typical REST APIs (it is obvious that params go to the external service).
Publisher identity for the marketplace. Not the resolution namespace (that is the toolbox source name).
name: github
namespace: github # "this spec was published by the GitHub organization"deprecated: true
deprecated_message: "Use github-mcp instead"
deprecated_by: github-mcp
actions:
- name: old_search
deprecated: true
deprecated_by: search_reposclictl run warns on stderr. clictl audit flags deprecated installs.
canonical: https://github.com/clictl/toolbox/blob/main/toolbox/g/github/github.yamlOptional source URL. clictl audit checks divergence from canonical.
Custom fields use the x- prefix:
x-clictl:
billing_sku: "tool-github-v1"
x-myplatform:
internal_id: 12345Unknown fields without x- are a validation warning (not error).
| Field | Default | Notes |
|---|---|---|
spec |
"1.0" |
Spec format version |
server.timeout |
30s |
Connection timeout |
discover |
auto | Automatic for stdio servers. Not needed in spec YAML. |
pricing.model |
free |
Omit pricing block for free tools |
params[].type |
string |
Most params are strings |
params[].required |
false |
Omit unless true |
params[].in (GET) |
query |
Omit for query params |
params[].in (POST/PUT/PATCH) |
body |
Omit for body params |
params[].in (path match) |
path |
Auto-detected from {name} in path |
output |
json |
Omit for JSON-returning actions |
mutable |
false |
Omit unless the action changes state |
retry.on |
[429, 500, 502, 503] |
Transient HTTP errors |
retry.max_attempts |
3 |
1 initial + 2 retries |
retry.backoff |
exponential |
Doubles each retry |
retry.delay |
1s |
Initial delay |
stream_timeout |
30s |
Idle timeout for streaming |
spec: "1.0"
name: nominatim
namespace: openstreetmap
description: Geocoding service powered by OpenStreetMap. Convert addresses to coordinates.
version: "1.0"
category: geo
tags: [geocoding, maps, location, coordinates]
server:
type: http
url: https://nominatim.openstreetmap.org
headers:
Accept: application/json
User-Agent: clictl/1.0
timeout: 10s
instructions: |
Free geocoding service with no API key required.
Rate limited to 1 request per second. Do not use for bulk geocoding.
actions:
- name: search
description: Forward geocode an address or place name to coordinates
method: GET
path: /search
params:
- name: q
required: true
description: Address or place name
example: "1600 Pennsylvania Ave, Washington DC"
- name: format
default: jsonv2
- name: limit
type: int
default: "5"
response:
example: |
[
{
"display_name": "White House, 1600, Pennsylvania Avenue, Washington, DC",
"lat": "38.8976633",
"lon": "-77.0365739",
"type": "house"
}
]
transform:
- type: json
select: [display_name, lat, lon, type]
- type: truncate
max_items: 5
- name: reverse
description: Reverse geocode coordinates to an address
method: GET
path: /reverse
params:
- name: lat
type: float
required: true
example: "38.8976633"
- name: lon
type: float
required: true
example: "-77.0365739"
- name: format
default: jsonv2spec: "1.0"
name: github
namespace: github
description: GitHub REST API for repositories, issues, pull requests, and users
version: "1.0"
category: developer
tags: [github, git, repos, issues, pull-requests]
pricing:
model: freemium
url: https://github.com/pricing
server:
type: http
url: https://api.github.com
headers:
Accept: application/vnd.github+json
X-GitHub-Api-Version: "2022-11-28"
timeout: 15s
auth:
env: GITHUB_TOKEN
header: Authorization
value: "Bearer ${GITHUB_TOKEN}"
instructions: |
Use for reading GitHub data: repos, issues, PRs, users.
For git operations, use the `git` CLI tool instead.
For GitHub Actions and advanced workflows, use the `gh` CLI.
Rate limit: 5,000 req/hour with token, 60/hour without.
actions:
- name: repos
description: List public repositories for a user
method: GET
path: /users/{username}/repos
params:
- name: username
required: true
example: "anthropics"
- name: sort
default: updated
description: "Sort by: created, updated, pushed, full_name"
- name: per_page
type: int
default: "10"
response:
example: |
[
{
"full_name": "anthropics/claude-code",
"description": "CLI for Claude",
"language": "TypeScript",
"stars": 25000
}
]
transform:
- type: json
select: [full_name, description, language, stargazers_count]
rename: { stargazers_count: stars }
- name: search_repos
description: Search repositories across all of GitHub
method: GET
path: /search/repositories
instructions: |
The `q` param uses GitHub search syntax:
- `language:go stars:>100` - filter by language and stars
- `org:anthropics` - scope to an organization
- `topic:cli` - filter by topic
params:
- name: q
required: true
description: Search query using GitHub search syntax
example: "language:go stars:>100 cli"
- name: sort
description: "Sort by: stars, forks, updated"
- name: per_page
type: int
default: "10"
assert:
- type: status
values: [200]
transform:
- type: json
extract: "$.items"
select: [full_name, description, language, stargazers_count]
rename: { stargazers_count: stars }spec: "1.0"
name: docker
namespace: docker
description: Docker container management
version: "1.0"
category: devops
tags: [docker, containers, images]
server:
type: command
shell: bash
requires:
- name: docker
check: "docker --version"
url: https://docs.docker.com/get-docker/
instructions: |
Manage Docker containers, images, and volumes.
Read-only operations (ps, images, logs) are safe.
Start/stop/remove operations will prompt for confirmation.
actions:
- name: ps
description: List running containers
run: "docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}'"
output: text
response:
example: |
CONTAINER ID NAMES STATUS IMAGE
a1b2c3d4e5f6 my-app Up 2 hours nginx:latest
- name: logs
description: Show recent container logs
run: "docker logs {{name}} --tail {{lines}}"
output: text
params:
- name: name
required: true
description: Container name or ID
example: "my-app"
- name: lines
type: int
default: "100"
- name: stop
description: Stop a running container
mutable: true
run: "docker stop {{name}}"
output: text
params:
- name: name
required: true
example: "my-app"spec: "1.0"
name: postgres-mcp
namespace: modelcontextprotocol
description: PostgreSQL database access via MCP
version: "1.0"
category: data
tags: [postgres, sql, database, query]
privacy:
pii: true
server:
type: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-postgres"]
env:
POSTGRES_CONNECTION_STRING: "${POSTGRES_CONNECTION_STRING}"
auth:
env: POSTGRES_CONNECTION_STRING
instructions: |
Provides read/write access to a PostgreSQL database.
Always run `describe_table` before writing queries to understand the schema.
Never DROP or TRUNCATE tables without explicit user confirmation.
# Static actions are metadata for search and documentation.
# Tools are discovered at runtime from the MCP server.
actions:
- name: query
description: Execute a SQL query against the connected database
output: json
instructions: "Always LIMIT results to 100 rows unless the user asks for more."
params:
- name: sql
required: true
example: "SELECT id, name FROM users LIMIT 10"
response:
example: |
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
- name: list_tables
description: List all tables in the database
output: json
response:
example: |
["users", "orders", "products"]
- name: describe_table
description: Show column names and types for a table
instructions: "Run this before writing queries to understand the schema."
params:
- name: table_name
required: true
example: "users"
deny:
- "drop_*"
- "truncate_*"
transforms:
"*":
- type: truncate
max_items: 100spec: "1.0"
name: pdf
namespace: anthropic
description: Extract and analyze PDF content
version: "1.0"
category: productivity
tags: [pdf, document, extract]
source:
repo: anthropics/claude-code
path: skills/pdf
ref: main
files:
- path: SKILL.md
sha256: a1b2c3d4e5f6...
- path: helpers/extract.py
sha256: f6e5d4c3b2a1...
depends:
- poppler
sandbox:
bash_allow: [uv, python3, pdftotext]
filesystem:
read: ["**/*.pdf", "**/*.md"]
write: ["**/*.md", "**/*.txt"]spec: "1.0"
name: github-translate
namespace: community
description: Search GitHub repos and translate descriptions
version: "1.0"
category: developer
tags: [github, translate, search]
depends:
- deepl
server:
type: http
url: https://api.github.com
headers:
Accept: application/vnd.github+json
auth:
env: GITHUB_TOKEN
header: Authorization
value: "Bearer ${GITHUB_TOKEN}"
actions:
- name: search_and_translate
description: Search repos and translate descriptions to German
method: GET
path: /search/repositories
params:
- name: q
required: true
example: "language:go cli"
transform:
- id: results
type: json
extract: "$.items"
select: [full_name, description]
- id: translated
input: results
type: pipe
run: "deepl translate --target_lang DE"
- type: truncate
input: translated
max_items: 5