@laitszkin/apollo-toolkit 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/align-project-documents/SKILL.md +94 -0
- package/align-project-documents/agents/openai.yaml +4 -0
- package/analyse-app-logs/LICENSE +21 -0
- package/analyse-app-logs/README.md +126 -0
- package/analyse-app-logs/SKILL.md +121 -0
- package/analyse-app-logs/agents/openai.yaml +4 -0
- package/analyse-app-logs/references/investigation-checklist.md +58 -0
- package/analyse-app-logs/references/log-signal-patterns.md +52 -0
- package/answering-questions-with-research/SKILL.md +46 -0
- package/answering-questions-with-research/agents/openai.yaml +4 -0
- package/bin/apollo-toolkit.js +7 -0
- package/commit-and-push/LICENSE +21 -0
- package/commit-and-push/README.md +26 -0
- package/commit-and-push/SKILL.md +70 -0
- package/commit-and-push/agents/openai.yaml +4 -0
- package/commit-and-push/references/branch-naming.md +15 -0
- package/commit-and-push/references/commit-messages.md +19 -0
- package/deep-research-topics/LICENSE +21 -0
- package/deep-research-topics/README.md +43 -0
- package/deep-research-topics/SKILL.md +84 -0
- package/deep-research-topics/agents/openai.yaml +4 -0
- package/develop-new-features/LICENSE +21 -0
- package/develop-new-features/README.md +52 -0
- package/develop-new-features/SKILL.md +105 -0
- package/develop-new-features/agents/openai.yaml +4 -0
- package/develop-new-features/references/testing-e2e.md +35 -0
- package/develop-new-features/references/testing-integration.md +42 -0
- package/develop-new-features/references/testing-property-based.md +44 -0
- package/develop-new-features/references/testing-unit.md +37 -0
- package/discover-edge-cases/CHANGELOG.md +19 -0
- package/discover-edge-cases/LICENSE +21 -0
- package/discover-edge-cases/README.md +87 -0
- package/discover-edge-cases/SKILL.md +124 -0
- package/discover-edge-cases/agents/openai.yaml +4 -0
- package/discover-edge-cases/references/architecture-edge-cases.md +41 -0
- package/discover-edge-cases/references/code-edge-cases.md +46 -0
- package/docs-to-voice/.env.example +106 -0
- package/docs-to-voice/CHANGELOG.md +71 -0
- package/docs-to-voice/LICENSE +21 -0
- package/docs-to-voice/README.md +118 -0
- package/docs-to-voice/SKILL.md +107 -0
- package/docs-to-voice/agents/openai.yaml +4 -0
- package/docs-to-voice/scripts/docs_to_voice.py +1385 -0
- package/docs-to-voice/scripts/docs_to_voice.sh +11 -0
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +210 -0
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +115 -0
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +43 -0
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +57 -0
- package/enhance-existing-features/CHANGELOG.md +35 -0
- package/enhance-existing-features/LICENSE +21 -0
- package/enhance-existing-features/README.md +54 -0
- package/enhance-existing-features/SKILL.md +120 -0
- package/enhance-existing-features/agents/openai.yaml +4 -0
- package/enhance-existing-features/references/e2e-tests.md +25 -0
- package/enhance-existing-features/references/integration-tests.md +30 -0
- package/enhance-existing-features/references/property-based-tests.md +33 -0
- package/enhance-existing-features/references/unit-tests.md +29 -0
- package/feature-propose/LICENSE +21 -0
- package/feature-propose/README.md +23 -0
- package/feature-propose/SKILL.md +107 -0
- package/feature-propose/agents/openai.yaml +4 -0
- package/feature-propose/references/enhancement-features.md +25 -0
- package/feature-propose/references/important-features.md +25 -0
- package/feature-propose/references/mvp-features.md +25 -0
- package/feature-propose/references/performance-features.md +25 -0
- package/financial-research/SKILL.md +208 -0
- package/financial-research/agents/openai.yaml +4 -0
- package/financial-research/assets/weekly_market_report_template.md +45 -0
- package/fix-github-issues/SKILL.md +98 -0
- package/fix-github-issues/agents/openai.yaml +4 -0
- package/fix-github-issues/scripts/list_issues.py +148 -0
- package/fix-github-issues/tests/test_list_issues.py +127 -0
- package/generate-spec/LICENSE +21 -0
- package/generate-spec/README.md +61 -0
- package/generate-spec/SKILL.md +96 -0
- package/generate-spec/agents/openai.yaml +4 -0
- package/generate-spec/references/templates/checklist.md +78 -0
- package/generate-spec/references/templates/spec.md +55 -0
- package/generate-spec/references/templates/tasks.md +35 -0
- package/generate-spec/scripts/create-specs +123 -0
- package/harden-app-security/CHANGELOG.md +27 -0
- package/harden-app-security/LICENSE +21 -0
- package/harden-app-security/README.md +46 -0
- package/harden-app-security/SKILL.md +127 -0
- package/harden-app-security/agents/openai.yaml +4 -0
- package/harden-app-security/references/agent-attack-catalog.md +117 -0
- package/harden-app-security/references/common-software-attack-catalog.md +168 -0
- package/harden-app-security/references/red-team-extreme-scenarios.md +81 -0
- package/harden-app-security/references/risk-checklist.md +78 -0
- package/harden-app-security/references/security-test-patterns-agent.md +101 -0
- package/harden-app-security/references/security-test-patterns-finance.md +88 -0
- package/harden-app-security/references/test-snippets.md +73 -0
- package/improve-observability/SKILL.md +114 -0
- package/improve-observability/agents/openai.yaml +4 -0
- package/learn-skill-from-conversations/CHANGELOG.md +15 -0
- package/learn-skill-from-conversations/LICENSE +22 -0
- package/learn-skill-from-conversations/README.md +47 -0
- package/learn-skill-from-conversations/SKILL.md +85 -0
- package/learn-skill-from-conversations/agents/openai.yaml +4 -0
- package/learn-skill-from-conversations/scripts/extract_recent_conversations.py +369 -0
- package/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +176 -0
- package/learning-error-book/SKILL.md +112 -0
- package/learning-error-book/agents/openai.yaml +4 -0
- package/learning-error-book/assets/error_book_template.md +66 -0
- package/learning-error-book/scripts/render_markdown_to_pdf.py +367 -0
- package/lib/cli.js +338 -0
- package/lib/installer.js +225 -0
- package/maintain-project-constraints/SKILL.md +109 -0
- package/maintain-project-constraints/agents/openai.yaml +4 -0
- package/maintain-skill-catalog/README.md +18 -0
- package/maintain-skill-catalog/SKILL.md +66 -0
- package/maintain-skill-catalog/agents/openai.yaml +4 -0
- package/novel-to-short-video/CHANGELOG.md +53 -0
- package/novel-to-short-video/LICENSE +21 -0
- package/novel-to-short-video/README.md +63 -0
- package/novel-to-short-video/SKILL.md +233 -0
- package/novel-to-short-video/agents/openai.yaml +4 -0
- package/novel-to-short-video/references/plan-template.md +71 -0
- package/novel-to-short-video/references/roles-json.md +41 -0
- package/open-github-issue/LICENSE +21 -0
- package/open-github-issue/README.md +97 -0
- package/open-github-issue/SKILL.md +119 -0
- package/open-github-issue/agents/openai.yaml +4 -0
- package/open-github-issue/scripts/open_github_issue.py +380 -0
- package/open-github-issue/tests/test_open_github_issue.py +159 -0
- package/open-source-pr-workflow/CHANGELOG.md +32 -0
- package/open-source-pr-workflow/LICENSE +21 -0
- package/open-source-pr-workflow/README.md +23 -0
- package/open-source-pr-workflow/SKILL.md +123 -0
- package/open-source-pr-workflow/agents/openai.yaml +4 -0
- package/openai-text-to-image-storyboard/.env.example +10 -0
- package/openai-text-to-image-storyboard/CHANGELOG.md +49 -0
- package/openai-text-to-image-storyboard/LICENSE +21 -0
- package/openai-text-to-image-storyboard/README.md +99 -0
- package/openai-text-to-image-storyboard/SKILL.md +107 -0
- package/openai-text-to-image-storyboard/agents/openai.yaml +4 -0
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +763 -0
- package/package.json +36 -0
- package/record-spending/SKILL.md +113 -0
- package/record-spending/agents/openai.yaml +4 -0
- package/record-spending/references/account-format.md +33 -0
- package/record-spending/references/workbook-layout.md +84 -0
- package/resolve-review-comments/SKILL.md +122 -0
- package/resolve-review-comments/agents/openai.yaml +4 -0
- package/resolve-review-comments/references/adoption-criteria.md +23 -0
- package/resolve-review-comments/scripts/review_threads.py +425 -0
- package/resolve-review-comments/tests/test_review_threads.py +74 -0
- package/review-change-set/LICENSE +21 -0
- package/review-change-set/README.md +55 -0
- package/review-change-set/SKILL.md +103 -0
- package/review-change-set/agents/openai.yaml +4 -0
- package/review-codebases/LICENSE +21 -0
- package/review-codebases/README.md +67 -0
- package/review-codebases/SKILL.md +109 -0
- package/review-codebases/agents/openai.yaml +4 -0
- package/scripts/install_skills.ps1 +283 -0
- package/scripts/install_skills.sh +262 -0
- package/scripts/validate_openai_agent_config.py +194 -0
- package/scripts/validate_skill_frontmatter.py +110 -0
- package/specs-to-project-docs/LICENSE +21 -0
- package/specs-to-project-docs/README.md +57 -0
- package/specs-to-project-docs/SKILL.md +111 -0
- package/specs-to-project-docs/agents/openai.yaml +4 -0
- package/specs-to-project-docs/references/templates/architecture.md +29 -0
- package/specs-to-project-docs/references/templates/configuration.md +29 -0
- package/specs-to-project-docs/references/templates/developer-guide.md +33 -0
- package/specs-to-project-docs/references/templates/docs-index.md +39 -0
- package/specs-to-project-docs/references/templates/features.md +25 -0
- package/specs-to-project-docs/references/templates/getting-started.md +38 -0
- package/specs-to-project-docs/references/templates/readme.md +49 -0
- package/systematic-debug/LICENSE +21 -0
- package/systematic-debug/README.md +81 -0
- package/systematic-debug/SKILL.md +59 -0
- package/systematic-debug/agents/openai.yaml +4 -0
- package/text-to-short-video/.env.example +36 -0
- package/text-to-short-video/LICENSE +21 -0
- package/text-to-short-video/README.md +82 -0
- package/text-to-short-video/SKILL.md +221 -0
- package/text-to-short-video/agents/openai.yaml +4 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +350 -0
- package/version-release/CHANGELOG.md +53 -0
- package/version-release/LICENSE +21 -0
- package/version-release/README.md +28 -0
- package/version-release/SKILL.md +94 -0
- package/version-release/agents/openai.yaml +4 -0
- package/version-release/references/branch-naming.md +15 -0
- package/version-release/references/changelog-writing.md +8 -0
- package/version-release/references/commit-messages.md +19 -0
- package/version-release/references/readme-writing.md +12 -0
- package/version-release/references/semantic-versioning.md +12 -0
- package/video-production/CHANGELOG.md +104 -0
- package/video-production/LICENSE +18 -0
- package/video-production/README.md +68 -0
- package/video-production/SKILL.md +213 -0
- package/video-production/agents/openai.yaml +4 -0
- package/video-production/references/plan-template.md +54 -0
- package/video-production/references/roles-json.md +41 -0
- package/weekly-financial-event-report/SKILL.md +195 -0
- package/weekly-financial-event-report/agents/openai.yaml +4 -0
- package/weekly-financial-event-report/assets/financial_event_report_template.md +53 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<"USAGE"
|
|
6
|
+
Usage:
|
|
7
|
+
./scripts/install_skills.sh [codex|openclaw|trae|all]...
|
|
8
|
+
|
|
9
|
+
Modes:
|
|
10
|
+
codex Install symlinks into ~/.codex/skills
|
|
11
|
+
openclaw Install symlinks into ~/.openclaw/workspace*/skills
|
|
12
|
+
trae Install symlinks into ~/.trae/skills
|
|
13
|
+
all Install all supported targets
|
|
14
|
+
|
|
15
|
+
Optional environment overrides:
|
|
16
|
+
CODEX_SKILLS_DIR Override codex skills destination path
|
|
17
|
+
OPENCLAW_HOME Override openclaw home path
|
|
18
|
+
TRAE_SKILLS_DIR Override trae skills destination path
|
|
19
|
+
APOLLO_TOOLKIT_HOME Override local install path used in curl/pipe mode
|
|
20
|
+
APOLLO_TOOLKIT_REPO_URL Override git repository URL used in curl/pipe mode
|
|
21
|
+
USAGE
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
SCRIPT_SOURCE="${BASH_SOURCE[0]-}"
|
|
25
|
+
TOOLKIT_REPO_URL="${APOLLO_TOOLKIT_REPO_URL:-https://github.com/LaiTszKin/apollo-toolkit.git}"
|
|
26
|
+
TOOLKIT_HOME="${APOLLO_TOOLKIT_HOME:-$HOME/.apollo-toolkit}"
|
|
27
|
+
|
|
28
|
+
show_banner() {
|
|
29
|
+
cat <<'BANNER'
|
|
30
|
+
+------------------------------------------+
|
|
31
|
+
| Apollo Toolkit |
|
|
32
|
+
| npm installer and skill linker |
|
|
33
|
+
+------------------------------------------+
|
|
34
|
+
BANNER
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
bootstrap_repo_if_needed() {
|
|
38
|
+
if [[ -d "$TOOLKIT_HOME/.git" ]]; then
|
|
39
|
+
git -C "$TOOLKIT_HOME" pull --ff-only >/dev/null
|
|
40
|
+
else
|
|
41
|
+
rm -rf "$TOOLKIT_HOME"
|
|
42
|
+
git clone --depth 1 "$TOOLKIT_REPO_URL" "$TOOLKIT_HOME" >/dev/null
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if [[ -n "$SCRIPT_SOURCE" && -f "$SCRIPT_SOURCE" ]]; then
|
|
47
|
+
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_SOURCE")" && pwd)"
|
|
48
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
49
|
+
else
|
|
50
|
+
# curl/pipe mode: use current directory only when it looks like this repo.
|
|
51
|
+
if find "$PWD" -mindepth 1 -maxdepth 1 -type d -exec test -f '{}/SKILL.md' ';' -print -quit | grep -q .; then
|
|
52
|
+
REPO_ROOT="$PWD"
|
|
53
|
+
else
|
|
54
|
+
bootstrap_repo_if_needed
|
|
55
|
+
REPO_ROOT="$TOOLKIT_HOME"
|
|
56
|
+
fi
|
|
57
|
+
SCRIPT_DIR="$REPO_ROOT/scripts"
|
|
58
|
+
fi
|
|
59
|
+
SELECTED_MODES=()
|
|
60
|
+
SKILL_PATHS=()
|
|
61
|
+
|
|
62
|
+
collect_skills() {
|
|
63
|
+
local dir
|
|
64
|
+
SKILL_PATHS=()
|
|
65
|
+
while IFS= read -r dir; do
|
|
66
|
+
if [[ -f "$dir/SKILL.md" ]]; then
|
|
67
|
+
SKILL_PATHS+=("$dir")
|
|
68
|
+
fi
|
|
69
|
+
done < <(find "$REPO_ROOT" -mindepth 1 -maxdepth 1 -type d | sort)
|
|
70
|
+
|
|
71
|
+
if [[ ${#SKILL_PATHS[@]} -eq 0 ]]; then
|
|
72
|
+
echo "No skill folders found in: $REPO_ROOT" >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
replace_with_symlink() {
|
|
78
|
+
local src="$1"
|
|
79
|
+
local target_root="$2"
|
|
80
|
+
local name target
|
|
81
|
+
|
|
82
|
+
name="$(basename "$src")"
|
|
83
|
+
target="$target_root/$name"
|
|
84
|
+
|
|
85
|
+
mkdir -p "$target_root"
|
|
86
|
+
if [[ -e "$target" || -L "$target" ]]; then
|
|
87
|
+
rm -rf "$target"
|
|
88
|
+
fi
|
|
89
|
+
ln -s "$src" "$target"
|
|
90
|
+
echo "[linked] $target -> $src"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
install_codex() {
|
|
94
|
+
local codex_skills_dir
|
|
95
|
+
codex_skills_dir="${CODEX_SKILLS_DIR:-$HOME/.codex/skills}"
|
|
96
|
+
|
|
97
|
+
echo "Installing to codex: $codex_skills_dir"
|
|
98
|
+
for src in "${SKILL_PATHS[@]}"; do
|
|
99
|
+
replace_with_symlink "$src" "$codex_skills_dir"
|
|
100
|
+
done
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
install_openclaw() {
|
|
104
|
+
local openclaw_home workspace skills_dir
|
|
105
|
+
local -a workspaces
|
|
106
|
+
|
|
107
|
+
openclaw_home="${OPENCLAW_HOME:-$HOME/.openclaw}"
|
|
108
|
+
|
|
109
|
+
workspaces=()
|
|
110
|
+
while IFS= read -r workspace; do
|
|
111
|
+
workspaces+=("$workspace")
|
|
112
|
+
done < <(find "$openclaw_home" -mindepth 1 -maxdepth 1 -type d -name 'workspace*' | sort)
|
|
113
|
+
|
|
114
|
+
if [[ ${#workspaces[@]} -eq 0 ]]; then
|
|
115
|
+
echo "No workspace directories found under: $openclaw_home" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
for workspace in "${workspaces[@]}"; do
|
|
120
|
+
skills_dir="$workspace/skills"
|
|
121
|
+
echo "Installing to openclaw workspace: $skills_dir"
|
|
122
|
+
for src in "${SKILL_PATHS[@]}"; do
|
|
123
|
+
replace_with_symlink "$src" "$skills_dir"
|
|
124
|
+
done
|
|
125
|
+
done
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
install_trae() {
|
|
129
|
+
local trae_skills_dir
|
|
130
|
+
trae_skills_dir="${TRAE_SKILLS_DIR:-$HOME/.trae/skills}"
|
|
131
|
+
|
|
132
|
+
echo "Installing to trae: $trae_skills_dir"
|
|
133
|
+
for src in "${SKILL_PATHS[@]}"; do
|
|
134
|
+
replace_with_symlink "$src" "$trae_skills_dir"
|
|
135
|
+
done
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
add_mode_once() {
|
|
139
|
+
local mode="$1"
|
|
140
|
+
local existing
|
|
141
|
+
|
|
142
|
+
if [[ ${#SELECTED_MODES[@]} -gt 0 ]]; then
|
|
143
|
+
for existing in "${SELECTED_MODES[@]}"; do
|
|
144
|
+
if [[ "$existing" == "$mode" ]]; then
|
|
145
|
+
return
|
|
146
|
+
fi
|
|
147
|
+
done
|
|
148
|
+
fi
|
|
149
|
+
SELECTED_MODES+=("$mode")
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
parse_mode() {
|
|
153
|
+
local mode="$1"
|
|
154
|
+
|
|
155
|
+
case "$mode" in
|
|
156
|
+
codex|openclaw|trae)
|
|
157
|
+
add_mode_once "$mode"
|
|
158
|
+
;;
|
|
159
|
+
all)
|
|
160
|
+
add_mode_once "codex"
|
|
161
|
+
add_mode_once "openclaw"
|
|
162
|
+
add_mode_once "trae"
|
|
163
|
+
;;
|
|
164
|
+
*)
|
|
165
|
+
echo "Invalid mode: $mode" >&2
|
|
166
|
+
usage
|
|
167
|
+
exit 1
|
|
168
|
+
;;
|
|
169
|
+
esac
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
read_choice_from_user() {
|
|
173
|
+
local prompt="$1"
|
|
174
|
+
local result
|
|
175
|
+
|
|
176
|
+
if [[ -t 0 ]]; then
|
|
177
|
+
read -r -p "$prompt" result
|
|
178
|
+
elif [[ -r /dev/tty ]]; then
|
|
179
|
+
read -r -p "$prompt" result < /dev/tty
|
|
180
|
+
else
|
|
181
|
+
echo "Interactive input unavailable. Pass mode arguments (e.g. codex/openclaw/trae/all)." >&2
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
printf '%s' "$result"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
choose_modes_interactive() {
|
|
189
|
+
local choice raw_choice
|
|
190
|
+
local -a choices
|
|
191
|
+
|
|
192
|
+
show_banner
|
|
193
|
+
echo
|
|
194
|
+
echo "Select install options (comma-separated):"
|
|
195
|
+
echo "1) codex (~/.codex/skills)"
|
|
196
|
+
echo "2) openclaw (~/.openclaw/workspace*/skills)"
|
|
197
|
+
echo "3) trae (~/.trae/skills)"
|
|
198
|
+
echo "4) all"
|
|
199
|
+
choice="$(read_choice_from_user 'Enter choice(s) [1-4]: ')"
|
|
200
|
+
|
|
201
|
+
IFS=',' read -r -a choices <<< "$choice"
|
|
202
|
+
for raw_choice in "${choices[@]}"; do
|
|
203
|
+
raw_choice="${raw_choice//[[:space:]]/}"
|
|
204
|
+
case "$raw_choice" in
|
|
205
|
+
1) add_mode_once "codex" ;;
|
|
206
|
+
2) add_mode_once "openclaw" ;;
|
|
207
|
+
3) add_mode_once "trae" ;;
|
|
208
|
+
4) add_mode_once "codex"; add_mode_once "openclaw"; add_mode_once "trae" ;;
|
|
209
|
+
*)
|
|
210
|
+
echo "Invalid choice: $raw_choice" >&2
|
|
211
|
+
exit 1
|
|
212
|
+
;;
|
|
213
|
+
esac
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
if [[ ${#SELECTED_MODES[@]} -eq 0 ]]; then
|
|
217
|
+
echo "No install option selected." >&2
|
|
218
|
+
exit 1
|
|
219
|
+
fi
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
resolve_modes() {
|
|
223
|
+
local mode
|
|
224
|
+
|
|
225
|
+
if [[ $# -eq 0 ]]; then
|
|
226
|
+
choose_modes_interactive
|
|
227
|
+
return
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
for mode in "$@"; do
|
|
231
|
+
parse_mode "$mode"
|
|
232
|
+
done
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main() {
|
|
236
|
+
local mode
|
|
237
|
+
SELECTED_MODES=()
|
|
238
|
+
|
|
239
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
240
|
+
usage
|
|
241
|
+
exit 0
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
resolve_modes "$@"
|
|
245
|
+
collect_skills
|
|
246
|
+
|
|
247
|
+
for mode in "${SELECTED_MODES[@]}"; do
|
|
248
|
+
case "$mode" in
|
|
249
|
+
codex) install_codex ;;
|
|
250
|
+
openclaw) install_openclaw ;;
|
|
251
|
+
trae) install_trae ;;
|
|
252
|
+
*)
|
|
253
|
+
usage
|
|
254
|
+
exit 1
|
|
255
|
+
;;
|
|
256
|
+
esac
|
|
257
|
+
done
|
|
258
|
+
|
|
259
|
+
echo "Done."
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
main "$@"
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate agents/openai.yaml for all top-level skills."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
TOP_LEVEL_ALLOWED_KEYS = {"interface", "dependencies", "policy"}
|
|
13
|
+
INTERFACE_REQUIRED_KEYS = {"display_name", "short_description", "default_prompt"}
|
|
14
|
+
INTERFACE_ALLOWED_KEYS = {
|
|
15
|
+
"display_name",
|
|
16
|
+
"short_description",
|
|
17
|
+
"default_prompt",
|
|
18
|
+
"icon_small",
|
|
19
|
+
"icon_large",
|
|
20
|
+
"brand_color",
|
|
21
|
+
}
|
|
22
|
+
HEX_COLOR_PATTERN = re.compile(r"^#[0-9A-Fa-f]{6}$")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def repo_root() -> Path:
|
|
26
|
+
return Path(__file__).resolve().parent.parent
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def iter_skill_dirs(root: Path) -> list[Path]:
|
|
30
|
+
return sorted(path for path in root.iterdir() if path.is_dir() and (path / "SKILL.md").is_file())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def extract_frontmatter(content: str) -> dict[str, object]:
|
|
34
|
+
lines = content.splitlines()
|
|
35
|
+
if not lines or lines[0].strip() != "---":
|
|
36
|
+
raise ValueError("SKILL.md must start with YAML frontmatter delimiter '---'.")
|
|
37
|
+
|
|
38
|
+
for index in range(1, len(lines)):
|
|
39
|
+
if lines[index].strip() == "---":
|
|
40
|
+
raw_frontmatter = "\n".join(lines[1:index])
|
|
41
|
+
parsed = yaml.safe_load(raw_frontmatter)
|
|
42
|
+
if not isinstance(parsed, dict):
|
|
43
|
+
raise ValueError("SKILL.md frontmatter must be a YAML mapping.")
|
|
44
|
+
return parsed
|
|
45
|
+
|
|
46
|
+
raise ValueError("SKILL.md frontmatter is missing the closing '---' delimiter.")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def require_non_empty_string(container: dict[str, object], key: str, context: str, errors: list[str]) -> None:
|
|
50
|
+
value = container.get(key)
|
|
51
|
+
if not isinstance(value, str) or not value.strip():
|
|
52
|
+
errors.append(f"{context}: '{key}' must be a non-empty string.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_dependencies(dependencies: object, context: str, errors: list[str]) -> None:
|
|
56
|
+
if not isinstance(dependencies, dict):
|
|
57
|
+
errors.append(f"{context}: 'dependencies' must be a mapping.")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
tools = dependencies.get("tools")
|
|
61
|
+
if tools is None:
|
|
62
|
+
return
|
|
63
|
+
if not isinstance(tools, list):
|
|
64
|
+
errors.append(f"{context}: 'dependencies.tools' must be a list.")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
for index, item in enumerate(tools):
|
|
68
|
+
item_context = f"{context}: dependencies.tools[{index}]"
|
|
69
|
+
if not isinstance(item, dict):
|
|
70
|
+
errors.append(f"{item_context} must be a mapping.")
|
|
71
|
+
continue
|
|
72
|
+
require_non_empty_string(item, "type", item_context, errors)
|
|
73
|
+
require_non_empty_string(item, "value", item_context, errors)
|
|
74
|
+
|
|
75
|
+
tool_type = item.get("type")
|
|
76
|
+
if isinstance(tool_type, str) and tool_type != "mcp":
|
|
77
|
+
errors.append(f"{item_context}: unsupported tool type '{tool_type}', only 'mcp' is allowed.")
|
|
78
|
+
|
|
79
|
+
for optional_key in ("description", "transport", "url"):
|
|
80
|
+
optional_value = item.get(optional_key)
|
|
81
|
+
if optional_value is not None and (not isinstance(optional_value, str) or not optional_value.strip()):
|
|
82
|
+
errors.append(f"{item_context}: '{optional_key}' must be a non-empty string when provided.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def validate_policy(policy: object, context: str, errors: list[str]) -> None:
|
|
86
|
+
if not isinstance(policy, dict):
|
|
87
|
+
errors.append(f"{context}: 'policy' must be a mapping.")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
allow_implicit = policy.get("allow_implicit_invocation")
|
|
91
|
+
if allow_implicit is not None and not isinstance(allow_implicit, bool):
|
|
92
|
+
errors.append(
|
|
93
|
+
f"{context}: 'policy.allow_implicit_invocation' must be a boolean when provided."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def validate_skill(skill_dir: Path) -> list[str]:
|
|
98
|
+
errors: list[str] = []
|
|
99
|
+
skill_md = skill_dir / "SKILL.md"
|
|
100
|
+
openai_yaml = skill_dir / "agents" / "openai.yaml"
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
skill_frontmatter = extract_frontmatter(skill_md.read_text(encoding="utf-8"))
|
|
104
|
+
except (OSError, ValueError, yaml.YAMLError) as exc:
|
|
105
|
+
return [f"{skill_md}: unable to read skill name for validation ({exc})."]
|
|
106
|
+
|
|
107
|
+
skill_name = skill_frontmatter.get("name")
|
|
108
|
+
if not isinstance(skill_name, str) or not skill_name.strip():
|
|
109
|
+
return [f"{skill_md}: frontmatter 'name' must be a non-empty string."]
|
|
110
|
+
|
|
111
|
+
if not openai_yaml.is_file():
|
|
112
|
+
return [f"{openai_yaml}: file is required for every skill."]
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
parsed = yaml.safe_load(openai_yaml.read_text(encoding="utf-8"))
|
|
116
|
+
except (OSError, yaml.YAMLError) as exc:
|
|
117
|
+
return [f"{openai_yaml}: invalid YAML ({exc})."]
|
|
118
|
+
|
|
119
|
+
if not isinstance(parsed, dict):
|
|
120
|
+
return [f"{openai_yaml}: top-level structure must be a YAML mapping."]
|
|
121
|
+
|
|
122
|
+
top_level_keys = set(parsed.keys())
|
|
123
|
+
unsupported_top_keys = sorted(top_level_keys - TOP_LEVEL_ALLOWED_KEYS)
|
|
124
|
+
if unsupported_top_keys:
|
|
125
|
+
errors.append(
|
|
126
|
+
f"{openai_yaml}: unsupported top-level keys: {', '.join(unsupported_top_keys)}."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
interface = parsed.get("interface")
|
|
130
|
+
if not isinstance(interface, dict):
|
|
131
|
+
errors.append(f"{openai_yaml}: 'interface' must be a mapping.")
|
|
132
|
+
return errors
|
|
133
|
+
|
|
134
|
+
missing_interface_keys = sorted(INTERFACE_REQUIRED_KEYS - set(interface.keys()))
|
|
135
|
+
if missing_interface_keys:
|
|
136
|
+
errors.append(
|
|
137
|
+
f"{openai_yaml}: missing required interface keys: {', '.join(missing_interface_keys)}."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
unsupported_interface_keys = sorted(set(interface.keys()) - INTERFACE_ALLOWED_KEYS)
|
|
141
|
+
if unsupported_interface_keys:
|
|
142
|
+
errors.append(
|
|
143
|
+
f"{openai_yaml}: unsupported interface keys: {', '.join(unsupported_interface_keys)}."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
for required_key in sorted(INTERFACE_REQUIRED_KEYS):
|
|
147
|
+
require_non_empty_string(interface, required_key, str(openai_yaml), errors)
|
|
148
|
+
|
|
149
|
+
default_prompt = interface.get("default_prompt")
|
|
150
|
+
expected_skill_ref = f"${skill_name.strip()}"
|
|
151
|
+
if isinstance(default_prompt, str) and expected_skill_ref not in default_prompt:
|
|
152
|
+
errors.append(
|
|
153
|
+
f"{openai_yaml}: interface.default_prompt must reference '{expected_skill_ref}'."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
brand_color = interface.get("brand_color")
|
|
157
|
+
if brand_color is not None:
|
|
158
|
+
if not isinstance(brand_color, str) or not HEX_COLOR_PATTERN.fullmatch(brand_color):
|
|
159
|
+
errors.append(f"{openai_yaml}: interface.brand_color must be a hex color like '#1A2B3C'.")
|
|
160
|
+
|
|
161
|
+
dependencies = parsed.get("dependencies")
|
|
162
|
+
if dependencies is not None:
|
|
163
|
+
validate_dependencies(dependencies, str(openai_yaml), errors)
|
|
164
|
+
|
|
165
|
+
policy = parsed.get("policy")
|
|
166
|
+
if policy is not None:
|
|
167
|
+
validate_policy(policy, str(openai_yaml), errors)
|
|
168
|
+
|
|
169
|
+
return errors
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main() -> int:
|
|
173
|
+
root = repo_root()
|
|
174
|
+
skill_dirs = iter_skill_dirs(root)
|
|
175
|
+
if not skill_dirs:
|
|
176
|
+
print("No top-level skill directories found.")
|
|
177
|
+
return 1
|
|
178
|
+
|
|
179
|
+
all_errors: list[str] = []
|
|
180
|
+
for skill_dir in skill_dirs:
|
|
181
|
+
all_errors.extend(validate_skill(skill_dir))
|
|
182
|
+
|
|
183
|
+
if all_errors:
|
|
184
|
+
print("agents/openai.yaml validation failed:")
|
|
185
|
+
for error in all_errors:
|
|
186
|
+
print(f"- {error}")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
print(f"agents/openai.yaml validation passed for {len(skill_dirs)} skills.")
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
sys.exit(main())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate SKILL.md frontmatter for all top-level skills."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
NAME_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
13
|
+
REQUIRED_KEYS = {"name", "description"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def repo_root() -> Path:
|
|
17
|
+
return Path(__file__).resolve().parent.parent
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def iter_skill_dirs(root: Path) -> list[Path]:
|
|
21
|
+
return sorted(path for path in root.iterdir() if path.is_dir() and (path / "SKILL.md").is_file())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_frontmatter(content: str) -> str:
|
|
25
|
+
lines = content.splitlines()
|
|
26
|
+
if not lines or lines[0].strip() != "---":
|
|
27
|
+
raise ValueError("SKILL.md must start with YAML frontmatter delimiter '---'.")
|
|
28
|
+
|
|
29
|
+
for index in range(1, len(lines)):
|
|
30
|
+
if lines[index].strip() == "---":
|
|
31
|
+
return "\n".join(lines[1:index])
|
|
32
|
+
|
|
33
|
+
raise ValueError("SKILL.md frontmatter is missing the closing '---' delimiter.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_skill(skill_dir: Path) -> list[str]:
|
|
37
|
+
errors: list[str] = []
|
|
38
|
+
skill_md = skill_dir / "SKILL.md"
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
content = skill_md.read_text(encoding="utf-8")
|
|
42
|
+
except OSError as exc:
|
|
43
|
+
return [f"{skill_md}: cannot read file ({exc})."]
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
frontmatter_text = extract_frontmatter(content)
|
|
47
|
+
except ValueError as exc:
|
|
48
|
+
return [f"{skill_md}: {exc}"]
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
52
|
+
except yaml.YAMLError as exc:
|
|
53
|
+
return [f"{skill_md}: invalid YAML in frontmatter ({exc})."]
|
|
54
|
+
|
|
55
|
+
if not isinstance(frontmatter, dict):
|
|
56
|
+
return [f"{skill_md}: frontmatter must be a YAML mapping."]
|
|
57
|
+
|
|
58
|
+
keys = set(frontmatter.keys())
|
|
59
|
+
if keys != REQUIRED_KEYS:
|
|
60
|
+
missing = sorted(REQUIRED_KEYS - keys)
|
|
61
|
+
extra = sorted(keys - REQUIRED_KEYS)
|
|
62
|
+
if missing:
|
|
63
|
+
errors.append(f"{skill_md}: missing required frontmatter keys: {', '.join(missing)}.")
|
|
64
|
+
if extra:
|
|
65
|
+
errors.append(f"{skill_md}: unsupported frontmatter keys: {', '.join(extra)}.")
|
|
66
|
+
|
|
67
|
+
name = frontmatter.get("name")
|
|
68
|
+
if not isinstance(name, str) or not name.strip():
|
|
69
|
+
errors.append(f"{skill_md}: 'name' must be a non-empty string.")
|
|
70
|
+
else:
|
|
71
|
+
normalized_name = name.strip()
|
|
72
|
+
if not NAME_PATTERN.fullmatch(normalized_name):
|
|
73
|
+
errors.append(
|
|
74
|
+
f"{skill_md}: 'name' must be kebab-case (lowercase letters, digits, and hyphens)."
|
|
75
|
+
)
|
|
76
|
+
if normalized_name != skill_dir.name:
|
|
77
|
+
errors.append(
|
|
78
|
+
f"{skill_md}: frontmatter name '{normalized_name}' must match folder name '{skill_dir.name}'."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
description = frontmatter.get("description")
|
|
82
|
+
if not isinstance(description, str) or not description.strip():
|
|
83
|
+
errors.append(f"{skill_md}: 'description' must be a non-empty string.")
|
|
84
|
+
|
|
85
|
+
return errors
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main() -> int:
|
|
89
|
+
root = repo_root()
|
|
90
|
+
skill_dirs = iter_skill_dirs(root)
|
|
91
|
+
if not skill_dirs:
|
|
92
|
+
print("No top-level skill directories found.")
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
all_errors: list[str] = []
|
|
96
|
+
for skill_dir in skill_dirs:
|
|
97
|
+
all_errors.extend(validate_skill(skill_dir))
|
|
98
|
+
|
|
99
|
+
if all_errors:
|
|
100
|
+
print("SKILL.md frontmatter validation failed:")
|
|
101
|
+
for error in all_errors:
|
|
102
|
+
print(f"- {error}")
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
print(f"SKILL.md frontmatter validation passed for {len(skill_dirs)} skills.")
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lai Tsz Kin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# specs-to-project-docs
|
|
2
|
+
|
|
3
|
+
A documentation skill that consolidates scattered spec files into standardized project docs. It produces a concise `README.md` plus a categorized document set grounded in real repository evidence.
|
|
4
|
+
|
|
5
|
+
## Core capabilities
|
|
6
|
+
|
|
7
|
+
- Scans `spec.md`, `tasks.md`, and `checklist.md` collections as documentation input.
|
|
8
|
+
- Reconciles spec claims against code, config, scripts, and deployment files.
|
|
9
|
+
- Standardizes both new and existing project docs into topic-based files for setup, configuration, architecture, features, and developer onboarding.
|
|
10
|
+
- Provides dedicated reference templates for the top-level README, the documentation index/reference list, and each category document.
|
|
11
|
+
- Deletes superseded spec source files after a successful conversion, unless they still need to stay active or be archived explicitly.
|
|
12
|
+
- Keeps unknown or unverifiable details explicit instead of guessing.
|
|
13
|
+
|
|
14
|
+
## Repository layout
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
.
|
|
18
|
+
├── SKILL.md
|
|
19
|
+
├── README.md
|
|
20
|
+
├── LICENSE
|
|
21
|
+
├── agents/
|
|
22
|
+
│ └── openai.yaml
|
|
23
|
+
└── references/
|
|
24
|
+
└── templates/
|
|
25
|
+
├── readme.md
|
|
26
|
+
├── docs-index.md
|
|
27
|
+
├── getting-started.md
|
|
28
|
+
├── configuration.md
|
|
29
|
+
├── architecture.md
|
|
30
|
+
├── features.md
|
|
31
|
+
└── developer-guide.md
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Default outputs
|
|
35
|
+
|
|
36
|
+
- `README.md`
|
|
37
|
+
- `docs/README.md`
|
|
38
|
+
- `docs/getting-started.md`
|
|
39
|
+
- `docs/configuration.md`
|
|
40
|
+
- `docs/architecture.md`
|
|
41
|
+
- `docs/features.md`
|
|
42
|
+
- `docs/developer-guide.md`
|
|
43
|
+
|
|
44
|
+
## Required doc coverage
|
|
45
|
+
|
|
46
|
+
- Installation and deployment
|
|
47
|
+
- Configuration and environment variables
|
|
48
|
+
- External services and API key acquisition/setup steps when applicable
|
|
49
|
+
- Project architecture
|
|
50
|
+
- Project feature introductions
|
|
51
|
+
- Information developers should understand before changing the project
|
|
52
|
+
|
|
53
|
+
## Notes
|
|
54
|
+
|
|
55
|
+
- Prefer code, config, and deployment files over stale spec text when they disagree.
|
|
56
|
+
- If the repository already has docs, rewrite them into the same categorized structure instead of leaving mixed documentation formats.
|
|
57
|
+
- Keep `README.md` short and let `docs/README.md` act as the reference list for deeper topic docs.
|