mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-08-28 04:24:58 +00:00
- convert.sh's SKILL.md finder now excludes .claude, .codex, .codex-plugin, .gemini, .hermes, .vibe, and docs — these are generated/symlinked mirrors for other tools, not source-of-truth skills. On platforms where git materializes symlinks as plain text (e.g. Git Bash on Windows), the mirrored files were being parsed as SKILL.md candidates and failing frontmatter extraction, flooding the run with "Skipping invalid frontmatter" warnings (#897). - Renamed the self-improving-agent (si) plugin's `status` and `review` skills to `memory-status` and `memory-review` so their bare `name:` values no longer collide with Claude Code's built-in `/status` and `/review` commands (#885). Updated all in-plugin references (CLAUDE.md, README, agents, hooks, references, settings.json, plugin.json) to the new `/si:memory-status` / `/si:memory-review` invocations. Fixes #897 (duplicate of #896), #885.
605 lines
16 KiB
Bash
Executable file
605 lines
16 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Usage:
|
|
# ./scripts/convert.sh [--tool <name>] [--out <dir>] [--help]
|
|
#
|
|
# Tools: antigravity, cursor, aider, kilocode, windsurf, opencode, augment, all
|
|
# Default: all
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
|
TOOL="all"
|
|
OUT_BASE="${REPO_ROOT}/integrations"
|
|
TODAY="$(date +%F)"
|
|
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
ok() {
|
|
echo -e "${GREEN}[OK]${NC} $*"
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[!!]${NC} $*"
|
|
}
|
|
|
|
err() {
|
|
echo -e "${RED}[ERR]${NC} $*" >&2
|
|
}
|
|
|
|
info() {
|
|
echo -e "${BLUE}[*]${NC} $*"
|
|
}
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
./scripts/convert.sh [--tool <name>] [--out <dir>] [--help]
|
|
|
|
Tools:
|
|
antigravity, cursor, aider, kilocode, windsurf, opencode, augment, all
|
|
|
|
Defaults:
|
|
--tool all
|
|
--out <repo-root>/integrations
|
|
USAGE
|
|
}
|
|
|
|
is_valid_tool() {
|
|
case "$1" in
|
|
antigravity|cursor|aider|kilocode|windsurf|opencode|augment|all) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
yaml_unquote() {
|
|
local value="$1"
|
|
value="${value#\"}"
|
|
value="${value%\"}"
|
|
value="${value#\'}"
|
|
value="${value%\'}"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
yaml_quote() {
|
|
local value="$1"
|
|
value="${value//\\/\\\\}"
|
|
value="${value//\"/\\\"}"
|
|
printf '"%s"' "$value"
|
|
}
|
|
|
|
init_count_vars() {
|
|
converted_antigravity=0
|
|
converted_cursor=0
|
|
converted_aider=0
|
|
converted_kilocode=0
|
|
converted_windsurf=0
|
|
converted_opencode=0
|
|
converted_augment=0
|
|
|
|
skipped_antigravity=0
|
|
skipped_cursor=0
|
|
skipped_aider=0
|
|
skipped_kilocode=0
|
|
skipped_windsurf=0
|
|
skipped_opencode=0
|
|
skipped_augment=0
|
|
}
|
|
|
|
inc_converted() {
|
|
local t="$1"
|
|
case "$t" in
|
|
antigravity) converted_antigravity=$((converted_antigravity + 1)) ;;
|
|
cursor) converted_cursor=$((converted_cursor + 1)) ;;
|
|
aider) converted_aider=$((converted_aider + 1)) ;;
|
|
kilocode) converted_kilocode=$((converted_kilocode + 1)) ;;
|
|
windsurf) converted_windsurf=$((converted_windsurf + 1)) ;;
|
|
opencode) converted_opencode=$((converted_opencode + 1)) ;;
|
|
augment) converted_augment=$((converted_augment + 1)) ;;
|
|
esac
|
|
}
|
|
|
|
inc_skipped() {
|
|
local t="$1"
|
|
case "$t" in
|
|
antigravity) skipped_antigravity=$((skipped_antigravity + 1)) ;;
|
|
cursor) skipped_cursor=$((skipped_cursor + 1)) ;;
|
|
aider) skipped_aider=$((skipped_aider + 1)) ;;
|
|
kilocode) skipped_kilocode=$((skipped_kilocode + 1)) ;;
|
|
windsurf) skipped_windsurf=$((skipped_windsurf + 1)) ;;
|
|
opencode) skipped_opencode=$((skipped_opencode + 1)) ;;
|
|
augment) skipped_augment=$((skipped_augment + 1)) ;;
|
|
esac
|
|
}
|
|
|
|
get_converted() {
|
|
local t="$1"
|
|
case "$t" in
|
|
antigravity) echo "$converted_antigravity" ;;
|
|
cursor) echo "$converted_cursor" ;;
|
|
aider) echo "$converted_aider" ;;
|
|
kilocode) echo "$converted_kilocode" ;;
|
|
windsurf) echo "$converted_windsurf" ;;
|
|
opencode) echo "$converted_opencode" ;;
|
|
augment) echo "$converted_augment" ;;
|
|
esac
|
|
}
|
|
|
|
get_skipped() {
|
|
local t="$1"
|
|
case "$t" in
|
|
antigravity) echo "$skipped_antigravity" ;;
|
|
cursor) echo "$skipped_cursor" ;;
|
|
aider) echo "$skipped_aider" ;;
|
|
kilocode) echo "$skipped_kilocode" ;;
|
|
windsurf) echo "$skipped_windsurf" ;;
|
|
opencode) echo "$skipped_opencode" ;;
|
|
augment) echo "$skipped_augment" ;;
|
|
esac
|
|
}
|
|
|
|
# Prints frontmatter fields as: name<TAB>description
|
|
extract_frontmatter() {
|
|
local file="$1"
|
|
awk '
|
|
BEGIN {
|
|
in_fm = 0
|
|
name = ""
|
|
desc = ""
|
|
in_desc_block = 0
|
|
}
|
|
|
|
function ltrim(s) {
|
|
sub(/^[[:space:]]+/, "", s)
|
|
return s
|
|
}
|
|
|
|
function rtrim(s) {
|
|
sub(/[[:space:]]+$/, "", s)
|
|
return s
|
|
}
|
|
|
|
function trim(s) {
|
|
return rtrim(ltrim(s))
|
|
}
|
|
|
|
function dequote(s, first, last) {
|
|
s = trim(s)
|
|
first = substr(s, 1, 1)
|
|
last = substr(s, length(s), 1)
|
|
if ((first == "\"" && last == "\"") || (first == "\047" && last == "\047")) {
|
|
s = substr(s, 2, length(s) - 2)
|
|
if (first == "\"") {
|
|
gsub(/\\\\/, "\\", s)
|
|
gsub(/\\"/, "\"", s)
|
|
} else {
|
|
gsub(/\047\047/, "\047", s)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
function append_desc(chunk) {
|
|
chunk = trim(chunk)
|
|
if (chunk == "") return
|
|
if (desc == "") {
|
|
desc = chunk
|
|
} else {
|
|
desc = desc " " chunk
|
|
}
|
|
}
|
|
|
|
NR == 1 {
|
|
if ($0 == "---") {
|
|
in_fm = 1
|
|
next
|
|
}
|
|
next
|
|
}
|
|
|
|
in_fm == 1 {
|
|
if ($0 == "---") {
|
|
in_fm = 0
|
|
next
|
|
}
|
|
|
|
if (in_desc_block == 1) {
|
|
if ($0 ~ /^[[:space:]]+/ || $0 == "") {
|
|
line = $0
|
|
sub(/^[[:space:]]+/, "", line)
|
|
append_desc(line)
|
|
next
|
|
}
|
|
in_desc_block = 0
|
|
}
|
|
|
|
if ($0 ~ /^name:[[:space:]]*/) {
|
|
line = $0
|
|
sub(/^name:[[:space:]]*/, "", line)
|
|
name = dequote(line)
|
|
next
|
|
}
|
|
|
|
if ($0 ~ /^description:[[:space:]]*/) {
|
|
line = $0
|
|
sub(/^description:[[:space:]]*/, "", line)
|
|
line = trim(line)
|
|
|
|
if (line ~ /^>[+-]?$/ || line ~ /^\|[+-]?$/) {
|
|
in_desc_block = 1
|
|
next
|
|
}
|
|
|
|
desc = dequote(line)
|
|
next
|
|
}
|
|
|
|
next
|
|
}
|
|
|
|
END {
|
|
printf "%s\t%s\n", trim(name), trim(desc)
|
|
}
|
|
' "$file"
|
|
}
|
|
|
|
extract_body() {
|
|
local file="$1"
|
|
awk '
|
|
BEGIN { in_fm = 0 }
|
|
|
|
NR == 1 {
|
|
if ($0 == "---") {
|
|
in_fm = 1
|
|
next
|
|
}
|
|
print
|
|
next
|
|
}
|
|
|
|
in_fm == 1 {
|
|
if ($0 == "---") {
|
|
in_fm = 0
|
|
next
|
|
}
|
|
next
|
|
}
|
|
|
|
{ print }
|
|
' "$file"
|
|
}
|
|
|
|
copy_supporting_dirs() {
|
|
local src_dir="$1"
|
|
local dst_dir="$2"
|
|
local d
|
|
for d in scripts references templates; do
|
|
if [[ -d "${src_dir}/${d}" ]]; then
|
|
cp -R "${src_dir}/${d}" "${dst_dir}/${d}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
append_aider_skill() {
|
|
local name="$1"
|
|
local description="$2"
|
|
local body_file="$3"
|
|
|
|
{
|
|
echo "---"
|
|
echo
|
|
echo "## ${name}"
|
|
echo "> ${description}"
|
|
echo
|
|
cat "$body_file"
|
|
echo
|
|
} >> "${AIDER_FILE}"
|
|
}
|
|
|
|
tool_title() {
|
|
case "$1" in
|
|
antigravity) echo "Antigravity" ;;
|
|
cursor) echo "Cursor" ;;
|
|
aider) echo "Aider" ;;
|
|
kilocode) echo "Kilo Code" ;;
|
|
windsurf) echo "Windsurf" ;;
|
|
opencode) echo "OpenCode" ;;
|
|
augment) echo "Augment" ;;
|
|
esac
|
|
}
|
|
|
|
write_tool_readme() {
|
|
local tool="$1"
|
|
local count="$2"
|
|
local out_dir="${OUT_BASE}/${tool}"
|
|
|
|
local format_line=""
|
|
local manual_install=""
|
|
local script_install="./scripts/install.sh --tool ${tool}"
|
|
local verify_step=""
|
|
local update_step=""
|
|
|
|
case "$tool" in
|
|
antigravity)
|
|
format_line='Directory skill bundles: `SKILL.md` with Antigravity frontmatter (`risk`, `source`, `date_added`) plus copied `scripts/`, `references/`, `templates/` when present.'
|
|
manual_install='Copy each folder from `integrations/antigravity/<skill-name>/` to `~/.gemini/antigravity/skills/<skill-name>/`.'
|
|
verify_step='Run `find ~/.gemini/antigravity/skills -name "SKILL.md" | wc -l` and confirm the count, then check your Gemini/Antigravity skill list.'
|
|
update_step='Re-run `./scripts/convert.sh --tool antigravity` and then reinstall with `./scripts/install.sh --tool antigravity`.'
|
|
;;
|
|
cursor)
|
|
format_line='Flat Cursor rules: `rules/<skill-name>.mdc` with Cursor-compatible frontmatter (`description`, `globs`, `alwaysApply`).'
|
|
manual_install='Copy `integrations/cursor/rules/*.mdc` into your project `.cursor/rules/` directory.'
|
|
verify_step='Open Cursor rules panel or run `find .cursor/rules -name "*.mdc" | wc -l` in your project.'
|
|
update_step='Re-run `./scripts/convert.sh --tool cursor` and then reinstall with `./scripts/install.sh --tool cursor --target <project-dir>`.'
|
|
;;
|
|
aider)
|
|
format_line='Single conventions file: `CONVENTIONS.md` concatenating all skills with separators and per-skill sections.'
|
|
manual_install='Copy `integrations/aider/CONVENTIONS.md` into your project root.'
|
|
verify_step='Run `aider --read CONVENTIONS.md` and confirm sections load (search for `## <skill-name>` entries).'
|
|
update_step='Re-run `./scripts/convert.sh --tool aider` and reinstall with `./scripts/install.sh --tool aider --target <project-dir>`.'
|
|
;;
|
|
kilocode)
|
|
format_line='Flat markdown rules: `rules/<skill-name>.md` with a title/description header and no frontmatter.'
|
|
manual_install='Copy `integrations/kilocode/rules/*.md` into your project `.kilocode/rules/` directory.'
|
|
verify_step='Run `find .kilocode/rules -name "*.md" | wc -l` in your project and confirm expected count.'
|
|
update_step='Re-run `./scripts/convert.sh --tool kilocode` and reinstall with `./scripts/install.sh --tool kilocode --target <project-dir>`.'
|
|
;;
|
|
windsurf)
|
|
format_line='Directory skill bundles: `skills/<skill-name>/SKILL.md` using Windsurf-compatible SKILL frontmatter (`name`, `description`) plus copied support folders.'
|
|
manual_install='Copy each folder from `integrations/windsurf/skills/<skill-name>/` to `.windsurf/skills/<skill-name>/` in your project.'
|
|
verify_step='Run `find .windsurf/skills -name "SKILL.md" | wc -l` and verify skills are listed in Windsurf.'
|
|
update_step='Re-run `./scripts/convert.sh --tool windsurf` and reinstall with `./scripts/install.sh --tool windsurf --target <project-dir>`.'
|
|
;;
|
|
opencode)
|
|
format_line='Directory skill bundles: `skills/<skill-name>/SKILL.md` with `compatibility: opencode` plus copied support folders.'
|
|
manual_install='Copy each folder from `integrations/opencode/skills/<skill-name>/` to `.opencode/skills/<skill-name>/` in your project.'
|
|
verify_step='Run `find .opencode/skills -name "SKILL.md" | wc -l` and confirm OpenCode shows installed skills.'
|
|
update_step='Re-run `./scripts/convert.sh --tool opencode` and reinstall with `./scripts/install.sh --tool opencode --target <project-dir>`.'
|
|
;;
|
|
augment)
|
|
format_line='Directory skill bundles: `skills/<skill-name>/SKILL.md` with Augment-compatible SKILL frontmatter (`name`, `description`) plus copied support folders.'
|
|
manual_install='Copy each folder from `integrations/augment/skills/<skill-name>/` to `.augment/skills/<skill-name>/` in your project.'
|
|
verify_step='Run `find .augment/skills -name "SKILL.md" | wc -l` and verify skills are listed in Augment.'
|
|
update_step='Re-run `./scripts/convert.sh --tool augment` and reinstall with `./scripts/install.sh --tool augment --target <project-dir>`.'
|
|
;;
|
|
esac
|
|
|
|
{
|
|
printf '# %s Integration\n\n' "$(tool_title "$tool")"
|
|
printf '\n\n' "$tool" "$tool"
|
|
printf 'This directory contains converted Claude Skills for **%s**.\n\n' "$(tool_title "$tool")"
|
|
printf '## Included Skills\n\n'
|
|
printf -- '- **%s** skills generated from this repository.\n\n' "$count"
|
|
printf '## Format\n\n'
|
|
printf '%s\n\n' "$format_line"
|
|
printf '## Install\n\n'
|
|
printf '### Manual\n\n'
|
|
printf '%s\n\n' "$manual_install"
|
|
printf '### Script\n\n'
|
|
printf '```bash\n'
|
|
printf 'git clone https://github.com/alirezarezvani/claude-skills.git\n'
|
|
printf 'cd claude-skills\n'
|
|
printf '%s\n' "$script_install"
|
|
printf '```\n\n'
|
|
printf '## Verify\n\n'
|
|
printf '%s\n\n' "$verify_step"
|
|
printf '## Update\n\n'
|
|
printf '%s\n\n' "$update_step"
|
|
printf '## Source Repository\n\n'
|
|
printf -- '- https://github.com/alirezarezvani/claude-skills\n'
|
|
} > "${out_dir}/README.md"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--tool)
|
|
TOOL="${2:-}"
|
|
shift 2
|
|
;;
|
|
--out)
|
|
OUT_BASE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
err "Unknown argument: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if ! is_valid_tool "$TOOL"; then
|
|
err "Invalid --tool value: ${TOOL}"
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
TOOLS="antigravity cursor aider kilocode windsurf opencode augment"
|
|
if [[ "$TOOL" != "all" ]]; then
|
|
TOOLS="$TOOL"
|
|
fi
|
|
|
|
SKILLS_TMP="$(mktemp)"
|
|
(
|
|
cd "$REPO_ROOT"
|
|
find . -mindepth 4 -maxdepth 6 -type f -name 'SKILL.md' \
|
|
-not -path './.git/*' \
|
|
-not -path './.claude/*' \
|
|
-not -path './.codex/*' \
|
|
-not -path './.codex-plugin/*' \
|
|
-not -path './.gemini/*' \
|
|
-not -path './.hermes/*' \
|
|
-not -path './.vibe/*' \
|
|
-not -path './integrations/*' \
|
|
-not -path './docs/*' \
|
|
| sort
|
|
) > "$SKILLS_TMP"
|
|
|
|
TOTAL_CANDIDATES="$(wc -l < "$SKILLS_TMP" | tr -d ' ')"
|
|
if [[ "$TOTAL_CANDIDATES" -eq 0 ]]; then
|
|
err "No skills found matching */*/SKILL.md"
|
|
rm -f "$SKILLS_TMP"
|
|
exit 1
|
|
fi
|
|
|
|
info "Found ${TOTAL_CANDIDATES} candidate skills"
|
|
|
|
for t in $TOOLS; do
|
|
rm -rf "${OUT_BASE}/${t}"
|
|
mkdir -p "${OUT_BASE}/${t}"
|
|
if [[ "$t" == "cursor" || "$t" == "kilocode" ]]; then
|
|
mkdir -p "${OUT_BASE}/${t}/rules"
|
|
fi
|
|
if [[ "$t" == "windsurf" || "$t" == "opencode" || "$t" == "augment" ]]; then
|
|
mkdir -p "${OUT_BASE}/${t}/skills"
|
|
fi
|
|
if [[ "$t" == "aider" ]]; then
|
|
AIDER_FILE="${OUT_BASE}/aider/CONVENTIONS.md"
|
|
{
|
|
echo "# Claude Skills — Aider Conventions"
|
|
echo "> Auto-generated from claude-skills. Do not edit manually."
|
|
echo "> Generated: ${TODAY}"
|
|
echo
|
|
} > "$AIDER_FILE"
|
|
fi
|
|
ok "Prepared output directory: ${OUT_BASE}/${t}"
|
|
done
|
|
|
|
init_count_vars
|
|
|
|
while IFS= read -r rel_path; do
|
|
src="${REPO_ROOT}/${rel_path#./}"
|
|
src_dir="$(dirname "$src")"
|
|
|
|
meta="$(extract_frontmatter "$src")"
|
|
name="${meta%%$'\t'*}"
|
|
description="${meta#*$'\t'}"
|
|
|
|
name="$(yaml_unquote "$name")"
|
|
description="$(yaml_unquote "$description")"
|
|
|
|
if [[ -z "$name" || -z "$description" ]]; then
|
|
for t in $TOOLS; do
|
|
inc_skipped "$t"
|
|
done
|
|
warn "Skipping invalid frontmatter: ${rel_path}"
|
|
continue
|
|
fi
|
|
|
|
body_tmp="$(mktemp)"
|
|
extract_body "$src" > "$body_tmp"
|
|
|
|
for t in $TOOLS; do
|
|
case "$t" in
|
|
antigravity)
|
|
out_dir="${OUT_BASE}/antigravity/${name}"
|
|
mkdir -p "$out_dir"
|
|
{
|
|
echo "---"
|
|
echo "name: $(yaml_quote "$name")"
|
|
echo "description: $(yaml_quote "$description")"
|
|
echo "risk: low"
|
|
echo "source: community"
|
|
echo "date_added: '${TODAY}'"
|
|
echo "---"
|
|
cat "$body_tmp"
|
|
} > "${out_dir}/SKILL.md"
|
|
copy_supporting_dirs "$src_dir" "$out_dir"
|
|
;;
|
|
cursor)
|
|
out_file="${OUT_BASE}/cursor/rules/${name}.mdc"
|
|
{
|
|
echo "---"
|
|
echo "description: $(yaml_quote "$description")"
|
|
echo "globs:"
|
|
echo "alwaysApply: false"
|
|
echo "---"
|
|
cat "$body_tmp"
|
|
} > "$out_file"
|
|
;;
|
|
aider)
|
|
append_aider_skill "$name" "$description" "$body_tmp"
|
|
;;
|
|
kilocode)
|
|
out_file="${OUT_BASE}/kilocode/rules/${name}.md"
|
|
{
|
|
echo "# ${name}"
|
|
echo "> ${description}"
|
|
echo
|
|
cat "$body_tmp"
|
|
} > "$out_file"
|
|
;;
|
|
windsurf)
|
|
out_dir="${OUT_BASE}/windsurf/skills/${name}"
|
|
mkdir -p "$out_dir"
|
|
{
|
|
echo "---"
|
|
echo "name: $(yaml_quote "$name")"
|
|
echo "description: $(yaml_quote "$description")"
|
|
echo "---"
|
|
cat "$body_tmp"
|
|
} > "${out_dir}/SKILL.md"
|
|
copy_supporting_dirs "$src_dir" "$out_dir"
|
|
;;
|
|
opencode)
|
|
out_dir="${OUT_BASE}/opencode/skills/${name}"
|
|
mkdir -p "$out_dir"
|
|
{
|
|
echo "---"
|
|
echo "name: $(yaml_quote "$name")"
|
|
echo "description: $(yaml_quote "$description")"
|
|
echo "compatibility: opencode"
|
|
echo "---"
|
|
cat "$body_tmp"
|
|
} > "${out_dir}/SKILL.md"
|
|
copy_supporting_dirs "$src_dir" "$out_dir"
|
|
;;
|
|
augment)
|
|
out_dir="${OUT_BASE}/augment/skills/${name}"
|
|
mkdir -p "$out_dir"
|
|
{
|
|
echo "---"
|
|
echo "name: $(yaml_quote "$name")"
|
|
echo "description: $(yaml_quote "$description")"
|
|
echo "---"
|
|
cat "$body_tmp"
|
|
} > "${out_dir}/SKILL.md"
|
|
copy_supporting_dirs "$src_dir" "$out_dir"
|
|
;;
|
|
*)
|
|
err "Unhandled tool: ${t}"
|
|
rm -f "$body_tmp" "$SKILLS_TMP"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
inc_converted "$t"
|
|
done
|
|
|
|
rm -f "$body_tmp"
|
|
ok "Converted ${name} (${rel_path})"
|
|
done < "$SKILLS_TMP"
|
|
|
|
rm -f "$SKILLS_TMP"
|
|
|
|
for t in $TOOLS; do
|
|
write_tool_readme "$t" "$(get_converted "$t")"
|
|
done
|
|
|
|
echo
|
|
info "Conversion summary"
|
|
for t in $TOOLS; do
|
|
echo " ${t}: $(get_converted "$t") converted, $(get_skipped "$t") skipped"
|
|
done
|
|
|
|
echo
|
|
ok "Done"
|