@flydocs/cli 0.6.0-alpha.13 → 0.6.0-alpha.20
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/dist/cli.js +281 -256
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +62 -66
- package/template/.claude/agents/implementation-agent.md +1 -1
- package/template/.claude/agents/pm-agent.md +1 -1
- package/template/.claude/commands/activate.md +1 -1
- package/template/.claude/commands/attach.md +1 -1
- package/template/.claude/commands/block.md +2 -2
- package/template/.claude/commands/capture.md +1 -1
- package/template/.claude/commands/close.md +1 -1
- package/template/.claude/commands/flydocs-setup.md +261 -58
- package/template/.claude/commands/flydocs-upgrade.md +26 -27
- package/template/.claude/commands/implement.md +1 -1
- package/template/.claude/commands/new-project.md +1 -1
- package/template/.claude/commands/onboard.md +275 -0
- package/template/.claude/commands/project-update.md +1 -1
- package/template/.claude/commands/refine.md +1 -1
- package/template/.claude/commands/review.md +1 -1
- package/template/.claude/commands/start-session.md +1 -1
- package/template/.claude/commands/status.md +1 -1
- package/template/.claude/commands/validate.md +1 -1
- package/template/.claude/commands/wrap-session.md +1 -1
- package/template/.claude/hooks/auto-approve.py +132 -0
- package/template/.claude/hooks/post-pr-check.py +108 -0
- package/template/.claude/hooks/post-transition-check.py +94 -0
- package/template/{.flydocs → .claude}/hooks/prompt-submit.py +167 -17
- package/template/.claude/hooks/session-start.py +146 -0
- package/template/.claude/hooks/stop-gate.py +109 -0
- package/template/.claude/settings.json +41 -4
- package/template/.claude/skills/README.md +23 -25
- package/template/.claude/skills/flydocs-workflow/SKILL.md +121 -34
- package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +9 -8
- package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +28 -17
- package/template/.claude/skills/flydocs-workflow/reference/graph-schema.md +116 -0
- package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +30 -15
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +1 -1
- package/template/.claude/skills/flydocs-workflow/reference/service-descriptor-schema.md +251 -0
- package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +26 -26
- package/template/.claude/skills/flydocs-workflow/scripts/_local/__init__.py +0 -0
- package/template/.claude/skills/{flydocs-local/scripts/flydocs_api.py → flydocs-workflow/scripts/_local/file_store.py} +133 -46
- package/template/.claude/skills/flydocs-workflow/scripts/flydocs_api.py +693 -0
- package/template/{.flydocs → .claude/skills/flydocs-workflow}/scripts/generate_manifest.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_build.py +132 -1
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_query.py +18 -5
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_session.py +1 -1
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_update.py +4 -4
- package/template/.claude/skills/{flydocs-context-graph → flydocs-workflow}/scripts/graph_utils.py +2 -1
- package/template/.claude/skills/flydocs-workflow/scripts/issues.py +489 -0
- package/template/.claude/skills/flydocs-workflow/scripts/projects.py +144 -0
- package/template/.claude/skills/flydocs-workflow/scripts/pull_services.py +128 -0
- package/template/.claude/skills/flydocs-workflow/scripts/push_service.py +132 -0
- package/template/.claude/skills/flydocs-workflow/scripts/session.py +54 -0
- package/template/.claude/skills/flydocs-workflow/scripts/workspace.py +860 -0
- package/template/.claude/skills/flydocs-workflow/session.md +16 -11
- package/template/.claude/skills/flydocs-workflow/stages/activate.md +13 -8
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +4 -4
- package/template/.claude/skills/flydocs-workflow/stages/close.md +1 -1
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +7 -7
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +5 -5
- package/template/.claude/skills/flydocs-workflow/stages/review.md +2 -2
- package/template/.claude/skills/flydocs-workflow/stages/validate.md +3 -1
- package/template/.claude/skills/flydocs-workflow/templates/pr/default.md +33 -0
- package/template/.cursor/agents/implementation-agent.md +1 -1
- package/template/.cursor/agents/pm-agent.md +2 -2
- package/template/.cursor/hooks.json +10 -3
- package/template/.env.example +6 -6
- package/template/.flydocs/config.json +2 -1
- package/template/.flydocs/templates/README.md +13 -14
- package/template/.flydocs/templates/quick-capture.md +4 -8
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +39 -32
- package/template/flydocs/README.md +1 -3
- package/template/flydocs/context/project.md +6 -3
- package/template/flydocs/design-system/README.md +3 -3
- package/template/manifest.json +17 -19
- package/template/.claude/skills/flydocs-cloud/SKILL.md +0 -138
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +0 -50
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +0 -22
- package/template/.claude/skills/flydocs-cloud/scripts/comment.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +0 -83
- package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +0 -35
- package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +0 -33
- package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +0 -39
- package/template/.claude/skills/flydocs-cloud/scripts/delete_milestone.py +0 -21
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +0 -33
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +0 -241
- package/template/.claude/skills/flydocs-cloud/scripts/generate_config.py +0 -125
- package/template/.claude/skills/flydocs-cloud/scripts/get_estimate_scale.py +0 -23
- package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +0 -24
- package/template/.claude/skills/flydocs-cloud/scripts/get_me.py +0 -103
- package/template/.claude/skills/flydocs-cloud/scripts/link.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +0 -44
- package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +0 -31
- package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/list_statuses.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +0 -19
- package/template/.claude/skills/flydocs-cloud/scripts/priority.py +0 -29
- package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +0 -45
- package/template/.claude/skills/flydocs-cloud/scripts/refresh_labels.py +0 -87
- package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +0 -54
- package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -54
- package/template/.claude/skills/flydocs-cloud/scripts/set_preferences.py +0 -49
- package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +0 -31
- package/template/.claude/skills/flydocs-cloud/scripts/set_status_mapping.py +0 -57
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +0 -28
- package/template/.claude/skills/flydocs-cloud/scripts/transition.py +0 -26
- package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +0 -36
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +0 -100
- package/template/.claude/skills/flydocs-cloud/scripts/update_milestone.py +0 -42
- package/template/.claude/skills/flydocs-cloud/scripts/validate_setup.py +0 -120
- package/template/.claude/skills/flydocs-context-graph/SKILL.md +0 -94
- package/template/.claude/skills/flydocs-context-graph/schema.md +0 -78
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +0 -338
- package/template/.claude/skills/flydocs-context7/SKILL.md +0 -105
- package/template/.claude/skills/flydocs-context7/cursor-rule.mdc +0 -49
- package/template/.claude/skills/flydocs-context7/scripts/context7.py +0 -293
- package/template/.claude/skills/flydocs-estimates/SKILL.md +0 -384
- package/template/.claude/skills/flydocs-figma/SKILL.md +0 -377
- package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +0 -108
- package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +0 -112
- package/template/.claude/skills/flydocs-local/SKILL.md +0 -103
- package/template/.claude/skills/flydocs-local/cursor-rule.mdc +0 -43
- package/template/.claude/skills/flydocs-local/scripts/assign.py +0 -29
- package/template/.claude/skills/flydocs-local/scripts/comment.py +0 -27
- package/template/.claude/skills/flydocs-local/scripts/create_issue.py +0 -44
- package/template/.claude/skills/flydocs-local/scripts/estimate.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/get_issue.py +0 -20
- package/template/.claude/skills/flydocs-local/scripts/link.py +0 -41
- package/template/.claude/skills/flydocs-local/scripts/list_issues.py +0 -50
- package/template/.claude/skills/flydocs-local/scripts/priority.py +0 -37
- package/template/.claude/skills/flydocs-local/scripts/project_update.py +0 -67
- package/template/.claude/skills/flydocs-local/scripts/status_summary.py +0 -16
- package/template/.claude/skills/flydocs-local/scripts/transition.py +0 -24
- package/template/.claude/skills/flydocs-local/scripts/update_description.py +0 -35
- package/template/.claude/skills/flydocs-local/scripts/update_issue.py +0 -84
- package/template/.flydocs/hooks/auto-approve.py +0 -71
- package/template/.flydocs/scripts/skill_manager.py +0 -541
- package/template/.flydocs/templates/bug.md +0 -166
- package/template/.flydocs/templates/chore.md +0 -110
- package/template/.flydocs/templates/feature.md +0 -173
- package/template/.flydocs/templates/idea.md +0 -122
- /package/template/{.flydocs → .claude}/hooks/post-edit.py +0 -0
- /package/template/.claude/skills/{flydocs-estimates/references → flydocs-workflow/reference}/provider-costs.md +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{bug.md → issues/bug.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{chore.md → issues/chore.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{feature.md → issues/feature.md} +0 -0
- /package/template/.claude/skills/flydocs-workflow/templates/{idea.md → issues/idea.md} +0 -0
|
@@ -1,541 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""FlyDocs Skill Manager — search, add, remove, and list community skills.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python3 skill_manager.py search <keyword>
|
|
6
|
-
python3 skill_manager.py add <source>
|
|
7
|
-
python3 skill_manager.py remove <name>
|
|
8
|
-
python3 skill_manager.py list
|
|
9
|
-
|
|
10
|
-
Source formats for add:
|
|
11
|
-
Known name: react-best-practices
|
|
12
|
-
Repo reference: vercel-labs/agent-skills:react-best-practices
|
|
13
|
-
GitHub URL: https://github.com/owner/repo/tree/main/skills/name
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
import argparse
|
|
17
|
-
import json
|
|
18
|
-
import os
|
|
19
|
-
import re
|
|
20
|
-
import shutil
|
|
21
|
-
import subprocess
|
|
22
|
-
import sys
|
|
23
|
-
import urllib.error
|
|
24
|
-
import urllib.request
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
RED = "\033[0;31m"
|
|
28
|
-
GREEN = "\033[0;32m"
|
|
29
|
-
BLUE = "\033[0;34m"
|
|
30
|
-
YELLOW = "\033[1;33m"
|
|
31
|
-
CYAN = "\033[0;36m"
|
|
32
|
-
NC = "\033[0m"
|
|
33
|
-
|
|
34
|
-
# ─── Curated community skills catalog ────────────────────────────────────────
|
|
35
|
-
# Mirrors install.sh COMMUNITY_SKILLS_MAP. Designed to swap with real API later.
|
|
36
|
-
|
|
37
|
-
COMMUNITY_SKILLS = [
|
|
38
|
-
{
|
|
39
|
-
"name": "react-best-practices",
|
|
40
|
-
"repo": "vercel-labs/agent-skills",
|
|
41
|
-
"description": "React/Next.js performance optimization (40+ rules from Vercel)",
|
|
42
|
-
"tags": ["react", "nextjs", "performance", "components"],
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"name": "web-design-guidelines",
|
|
46
|
-
"repo": "vercel-labs/agent-skills",
|
|
47
|
-
"description": "UI/UX accessibility and design patterns (100+ rules)",
|
|
48
|
-
"tags": ["design", "accessibility", "ux", "ui", "nextjs", "web"],
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"name": "convex-best-practices",
|
|
52
|
-
"repo": "waynesutton/convexskills",
|
|
53
|
-
"description": "Convex database patterns and best practices",
|
|
54
|
-
"tags": ["convex", "database", "backend"],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"name": "convex-functions",
|
|
58
|
-
"repo": "waynesutton/convexskills",
|
|
59
|
-
"description": "Convex query/mutation/action patterns",
|
|
60
|
-
"tags": ["convex", "functions", "query", "mutation"],
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "tailwind-v4",
|
|
64
|
-
"repo": "blencorp/claude-code-kit",
|
|
65
|
-
"description": "Tailwind CSS v4 patterns and configuration",
|
|
66
|
-
"tags": ["tailwind", "css", "styling"],
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"name": "clerk-auth",
|
|
70
|
-
"repo": "blencorp/claude-code-kit",
|
|
71
|
-
"description": "Clerk authentication patterns",
|
|
72
|
-
"tags": ["clerk", "auth", "authentication"],
|
|
73
|
-
},
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def find_project_root():
|
|
81
|
-
"""Find the project root by walking up to .flydocs/config.json."""
|
|
82
|
-
path = os.getcwd()
|
|
83
|
-
while True:
|
|
84
|
-
if os.path.isfile(os.path.join(path, ".flydocs", "config.json")):
|
|
85
|
-
return path
|
|
86
|
-
parent = os.path.dirname(path)
|
|
87
|
-
if parent == path:
|
|
88
|
-
break
|
|
89
|
-
path = parent
|
|
90
|
-
return os.getcwd()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def load_config(root):
|
|
94
|
-
"""Load .flydocs/config.json."""
|
|
95
|
-
config_path = os.path.join(root, ".flydocs", "config.json")
|
|
96
|
-
if not os.path.isfile(config_path):
|
|
97
|
-
print(f"{RED}✗{NC} No .flydocs/config.json found. Is FlyDocs installed?", file=sys.stderr)
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
with open(config_path, "r", encoding="utf-8") as f:
|
|
100
|
-
return json.load(f)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def save_config(root, config):
|
|
104
|
-
"""Save .flydocs/config.json."""
|
|
105
|
-
config_path = os.path.join(root, ".flydocs", "config.json")
|
|
106
|
-
with open(config_path, "w", encoding="utf-8") as f:
|
|
107
|
-
json.dump(config, f, indent=2)
|
|
108
|
-
f.write("\n")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def parse_frontmatter(text):
|
|
112
|
-
"""Parse YAML frontmatter from a SKILL.md file.
|
|
113
|
-
|
|
114
|
-
Duplicated from generate_manifest.py — each script is self-contained.
|
|
115
|
-
"""
|
|
116
|
-
match = re.match(r"^---\s*\n(.*?)\n---", text, re.DOTALL)
|
|
117
|
-
if not match:
|
|
118
|
-
return None
|
|
119
|
-
|
|
120
|
-
block = match.group(1)
|
|
121
|
-
result = {}
|
|
122
|
-
current_key = None
|
|
123
|
-
current_mode = None # "scalar", "block", "list"
|
|
124
|
-
current_lines = []
|
|
125
|
-
|
|
126
|
-
for line in block.split("\n"):
|
|
127
|
-
key_match = re.match(r"^(\w[\w-]*):\s*(.*)", line)
|
|
128
|
-
if key_match and not line.startswith(" "):
|
|
129
|
-
if current_key is not None:
|
|
130
|
-
result[current_key] = _flush(current_mode, current_lines)
|
|
131
|
-
|
|
132
|
-
current_key = key_match.group(1)
|
|
133
|
-
value = key_match.group(2).strip()
|
|
134
|
-
|
|
135
|
-
if value in ("|", ">"):
|
|
136
|
-
current_mode = "block"
|
|
137
|
-
current_lines = []
|
|
138
|
-
elif value == "":
|
|
139
|
-
current_mode = "list"
|
|
140
|
-
current_lines = []
|
|
141
|
-
else:
|
|
142
|
-
current_mode = "scalar"
|
|
143
|
-
current_lines = [value.strip("\"'")]
|
|
144
|
-
continue
|
|
145
|
-
|
|
146
|
-
stripped = line.strip()
|
|
147
|
-
if current_key is not None:
|
|
148
|
-
if current_mode == "list" and stripped.startswith("- "):
|
|
149
|
-
current_lines.append(stripped[2:].strip().strip("\"'"))
|
|
150
|
-
elif current_mode == "block":
|
|
151
|
-
current_lines.append(line.lstrip())
|
|
152
|
-
|
|
153
|
-
if current_key is not None:
|
|
154
|
-
result[current_key] = _flush(current_mode, current_lines)
|
|
155
|
-
|
|
156
|
-
return result
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _flush(mode, lines):
|
|
160
|
-
if mode == "scalar":
|
|
161
|
-
return lines[0] if lines else ""
|
|
162
|
-
elif mode == "list":
|
|
163
|
-
return lines
|
|
164
|
-
elif mode == "block":
|
|
165
|
-
return "\n".join(lines).strip()
|
|
166
|
-
return ""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def is_platform_skill(name):
|
|
170
|
-
"""Check if a skill name is platform-managed."""
|
|
171
|
-
return name.startswith("flydocs-")
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def regenerate_manifest(root):
|
|
175
|
-
"""Run generate_manifest.py to update CLAUDE.md/AGENTS.md."""
|
|
176
|
-
script = os.path.join(root, ".flydocs", "scripts", "generate_manifest.py")
|
|
177
|
-
if not os.path.isfile(script):
|
|
178
|
-
print(f"{YELLOW}⚠{NC} generate_manifest.py not found — skipping manifest update.")
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
try:
|
|
182
|
-
subprocess.run(
|
|
183
|
-
[sys.executable, script, "--root", root],
|
|
184
|
-
check=True,
|
|
185
|
-
capture_output=True,
|
|
186
|
-
text=True,
|
|
187
|
-
)
|
|
188
|
-
print(f"{GREEN}✓{NC} Regenerated skill manifest")
|
|
189
|
-
except subprocess.CalledProcessError as e:
|
|
190
|
-
print(f"{YELLOW}⚠{NC} Manifest regeneration failed: {e.stderr.strip()}")
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# ─── search ──────────────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def cmd_search(args):
|
|
197
|
-
"""Search curated community skills catalog."""
|
|
198
|
-
keyword = args.keyword.lower()
|
|
199
|
-
matches = []
|
|
200
|
-
|
|
201
|
-
for skill in COMMUNITY_SKILLS:
|
|
202
|
-
searchable = f"{skill['name']} {skill['description']} {' '.join(skill['tags'])}".lower()
|
|
203
|
-
if keyword in searchable:
|
|
204
|
-
matches.append(skill)
|
|
205
|
-
|
|
206
|
-
if not matches:
|
|
207
|
-
print(f"\n No skills found matching '{args.keyword}'.")
|
|
208
|
-
print(f" Browse more at: https://skills.sh/\n")
|
|
209
|
-
return
|
|
210
|
-
|
|
211
|
-
print(f"\n Found {len(matches)} skill(s) matching '{args.keyword}':\n")
|
|
212
|
-
for skill in matches:
|
|
213
|
-
print(f" {CYAN}{skill['name']}{NC}")
|
|
214
|
-
print(f" {skill['description']}")
|
|
215
|
-
print(f" Source: {skill['repo']}")
|
|
216
|
-
print(f" Tags: {', '.join(skill['tags'])}")
|
|
217
|
-
print()
|
|
218
|
-
|
|
219
|
-
print(f" Install with: flydocs skills add <name>")
|
|
220
|
-
print(f" Browse more at: https://skills.sh/\n")
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# ─── add ─────────────────────────────────────────────────────────────────────
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def parse_source(source):
|
|
227
|
-
"""Parse a skill source into (repo, skill_name).
|
|
228
|
-
|
|
229
|
-
Accepts:
|
|
230
|
-
- Known name: "react-best-practices"
|
|
231
|
-
- Repo reference: "vercel-labs/agent-skills:react-best-practices"
|
|
232
|
-
- GitHub URL: "https://github.com/owner/repo/tree/main/skills/name"
|
|
233
|
-
"""
|
|
234
|
-
# GitHub URL
|
|
235
|
-
url_match = re.match(
|
|
236
|
-
r"https?://github\.com/([^/]+/[^/]+)/tree/[^/]+/skills/([^/]+)/?$",
|
|
237
|
-
source,
|
|
238
|
-
)
|
|
239
|
-
if url_match:
|
|
240
|
-
return url_match.group(1), url_match.group(2)
|
|
241
|
-
|
|
242
|
-
# Repo reference (owner/repo:skill-name)
|
|
243
|
-
if ":" in source:
|
|
244
|
-
repo, name = source.rsplit(":", 1)
|
|
245
|
-
if "/" in repo:
|
|
246
|
-
return repo, name
|
|
247
|
-
|
|
248
|
-
# Known name — lookup in curated catalog
|
|
249
|
-
for skill in COMMUNITY_SKILLS:
|
|
250
|
-
if skill["name"] == source:
|
|
251
|
-
return skill["repo"], skill["name"]
|
|
252
|
-
|
|
253
|
-
print(f"{RED}✗{NC} Unknown skill: {source}", file=sys.stderr)
|
|
254
|
-
print(f" Try: flydocs skills search <keyword>", file=sys.stderr)
|
|
255
|
-
print(f" Or use full reference: owner/repo:skill-name", file=sys.stderr)
|
|
256
|
-
sys.exit(1)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def download_skill_tree(repo, skill_name, target_dir):
|
|
260
|
-
"""Download a skill directory tree via GitHub Contents API (recursive)."""
|
|
261
|
-
api_url = f"https://api.github.com/repos/{repo}/contents/skills/{skill_name}"
|
|
262
|
-
|
|
263
|
-
def fetch_json(url):
|
|
264
|
-
req = urllib.request.Request(url, headers={"User-Agent": "flydocs-cli"})
|
|
265
|
-
try:
|
|
266
|
-
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
267
|
-
return json.loads(resp.read().decode("utf-8"))
|
|
268
|
-
except urllib.error.HTTPError as e:
|
|
269
|
-
if e.code == 404:
|
|
270
|
-
return None
|
|
271
|
-
raise
|
|
272
|
-
|
|
273
|
-
def download_recursive(url, dest_dir):
|
|
274
|
-
items = fetch_json(url)
|
|
275
|
-
if items is None:
|
|
276
|
-
return False
|
|
277
|
-
|
|
278
|
-
# Single file response comes as dict, directory as list
|
|
279
|
-
if isinstance(items, dict):
|
|
280
|
-
items = [items]
|
|
281
|
-
|
|
282
|
-
os.makedirs(dest_dir, exist_ok=True)
|
|
283
|
-
|
|
284
|
-
for item in items:
|
|
285
|
-
dest_path = os.path.join(dest_dir, item["name"])
|
|
286
|
-
if item["type"] == "file":
|
|
287
|
-
if item.get("download_url"):
|
|
288
|
-
req = urllib.request.Request(
|
|
289
|
-
item["download_url"],
|
|
290
|
-
headers={"User-Agent": "flydocs-cli"},
|
|
291
|
-
)
|
|
292
|
-
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
293
|
-
content = resp.read()
|
|
294
|
-
with open(dest_path, "wb") as f:
|
|
295
|
-
f.write(content)
|
|
296
|
-
elif item["type"] == "dir":
|
|
297
|
-
download_recursive(item["url"], dest_path)
|
|
298
|
-
|
|
299
|
-
return True
|
|
300
|
-
|
|
301
|
-
return download_recursive(api_url, target_dir)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def cmd_add(args):
|
|
305
|
-
"""Install a community skill."""
|
|
306
|
-
root = find_project_root()
|
|
307
|
-
config = load_config(root)
|
|
308
|
-
source = args.source
|
|
309
|
-
|
|
310
|
-
# Platform guard (check before parse_source which would reject as "unknown")
|
|
311
|
-
if is_platform_skill(source):
|
|
312
|
-
print(f"{RED}✗{NC} Cannot install platform skill '{source}'.")
|
|
313
|
-
print(f" Platform skills (flydocs-*) are managed by the installer.")
|
|
314
|
-
sys.exit(1)
|
|
315
|
-
|
|
316
|
-
repo, skill_name = parse_source(source)
|
|
317
|
-
|
|
318
|
-
# Platform guard (for repo references that resolve to a flydocs-* name)
|
|
319
|
-
if is_platform_skill(skill_name):
|
|
320
|
-
print(f"{RED}✗{NC} Cannot install platform skill '{skill_name}'.")
|
|
321
|
-
print(f" Platform skills (flydocs-*) are managed by the installer.")
|
|
322
|
-
sys.exit(1)
|
|
323
|
-
|
|
324
|
-
# Already installed check
|
|
325
|
-
skills_dir = os.path.join(root, ".claude", "skills", skill_name)
|
|
326
|
-
if os.path.isdir(skills_dir):
|
|
327
|
-
print(f"{YELLOW}⚠{NC} Skill '{skill_name}' is already installed.")
|
|
328
|
-
print(f" Remove first: flydocs skills remove {skill_name}")
|
|
329
|
-
sys.exit(1)
|
|
330
|
-
|
|
331
|
-
print(f"\n{BLUE}→{NC} Installing {CYAN}{skill_name}{NC} from {repo}...\n")
|
|
332
|
-
|
|
333
|
-
# Download
|
|
334
|
-
try:
|
|
335
|
-
success = download_skill_tree(repo, skill_name, skills_dir)
|
|
336
|
-
except Exception as e:
|
|
337
|
-
if os.path.isdir(skills_dir):
|
|
338
|
-
shutil.rmtree(skills_dir)
|
|
339
|
-
print(f"{RED}✗{NC} Download failed: {e}")
|
|
340
|
-
sys.exit(1)
|
|
341
|
-
|
|
342
|
-
if not success:
|
|
343
|
-
if os.path.isdir(skills_dir):
|
|
344
|
-
shutil.rmtree(skills_dir)
|
|
345
|
-
print(f"{RED}✗{NC} Skill not found at {repo}/skills/{skill_name}")
|
|
346
|
-
sys.exit(1)
|
|
347
|
-
|
|
348
|
-
# Validate SKILL.md exists
|
|
349
|
-
skill_md = os.path.join(skills_dir, "SKILL.md")
|
|
350
|
-
if not os.path.isfile(skill_md):
|
|
351
|
-
shutil.rmtree(skills_dir)
|
|
352
|
-
print(f"{RED}✗{NC} Invalid skill: SKILL.md not found.")
|
|
353
|
-
sys.exit(1)
|
|
354
|
-
|
|
355
|
-
# Validate frontmatter
|
|
356
|
-
with open(skill_md, "r", encoding="utf-8") as f:
|
|
357
|
-
content = f.read()
|
|
358
|
-
|
|
359
|
-
fm = parse_frontmatter(content)
|
|
360
|
-
if fm is None or not fm.get("name") or not fm.get("description"):
|
|
361
|
-
shutil.rmtree(skills_dir)
|
|
362
|
-
print(f"{RED}✗{NC} Invalid skill: SKILL.md missing required frontmatter (name, description).")
|
|
363
|
-
sys.exit(1)
|
|
364
|
-
|
|
365
|
-
if not fm.get("triggers"):
|
|
366
|
-
print(f"{YELLOW}⚠{NC} SKILL.md has no triggers — skill won't appear in manifest index.")
|
|
367
|
-
|
|
368
|
-
print(f"{GREEN}✓{NC} Downloaded skill files")
|
|
369
|
-
|
|
370
|
-
# Copy cursor-rule.mdc if present
|
|
371
|
-
cursor_rule = os.path.join(skills_dir, "cursor-rule.mdc")
|
|
372
|
-
if os.path.isfile(cursor_rule):
|
|
373
|
-
cursor_rules_dir = os.path.join(root, ".cursor", "rules")
|
|
374
|
-
os.makedirs(cursor_rules_dir, exist_ok=True)
|
|
375
|
-
dest = os.path.join(cursor_rules_dir, f"{skill_name}.mdc")
|
|
376
|
-
shutil.copy2(cursor_rule, dest)
|
|
377
|
-
print(f"{GREEN}✓{NC} Installed cursor rule")
|
|
378
|
-
|
|
379
|
-
# Update config.json skills.installed[]
|
|
380
|
-
installed = config.get("skills", {}).get("installed", [])
|
|
381
|
-
entry = f"{repo}/{skill_name}"
|
|
382
|
-
if entry not in installed:
|
|
383
|
-
installed.append(entry)
|
|
384
|
-
if "skills" not in config:
|
|
385
|
-
config["skills"] = {"installed": [], "custom": []}
|
|
386
|
-
config["skills"]["installed"] = installed
|
|
387
|
-
save_config(root, config)
|
|
388
|
-
print(f"{GREEN}✓{NC} Updated config.json")
|
|
389
|
-
|
|
390
|
-
# Regenerate manifest
|
|
391
|
-
regenerate_manifest(root)
|
|
392
|
-
|
|
393
|
-
print(f"\n{GREEN}✓{NC} Installed {CYAN}{skill_name}{NC}\n")
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
# ─── remove ──────────────────────────────────────────────────────────────────
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
def cmd_remove(args):
|
|
400
|
-
"""Remove an installed community skill."""
|
|
401
|
-
root = find_project_root()
|
|
402
|
-
config = load_config(root)
|
|
403
|
-
skill_name = args.name
|
|
404
|
-
|
|
405
|
-
# Platform guard
|
|
406
|
-
if is_platform_skill(skill_name):
|
|
407
|
-
print(f"{RED}✗{NC} Cannot remove platform skill '{skill_name}'.")
|
|
408
|
-
print(f" Platform skills (flydocs-*) are managed by the installer.")
|
|
409
|
-
sys.exit(1)
|
|
410
|
-
|
|
411
|
-
skills_dir = os.path.join(root, ".claude", "skills", skill_name)
|
|
412
|
-
if not os.path.isdir(skills_dir):
|
|
413
|
-
print(f"{RED}✗{NC} Skill '{skill_name}' is not installed.")
|
|
414
|
-
sys.exit(1)
|
|
415
|
-
|
|
416
|
-
# Remove skill directory
|
|
417
|
-
shutil.rmtree(skills_dir)
|
|
418
|
-
print(f"{GREEN}✓{NC} Removed skill directory")
|
|
419
|
-
|
|
420
|
-
# Remove cursor rule
|
|
421
|
-
cursor_rule = os.path.join(root, ".cursor", "rules", f"{skill_name}.mdc")
|
|
422
|
-
if os.path.isfile(cursor_rule):
|
|
423
|
-
os.remove(cursor_rule)
|
|
424
|
-
print(f"{GREEN}✓{NC} Removed cursor rule")
|
|
425
|
-
|
|
426
|
-
# Update config.json
|
|
427
|
-
installed = config.get("skills", {}).get("installed", [])
|
|
428
|
-
config["skills"]["installed"] = [
|
|
429
|
-
s for s in installed if not s.endswith(f"/{skill_name}")
|
|
430
|
-
]
|
|
431
|
-
save_config(root, config)
|
|
432
|
-
print(f"{GREEN}✓{NC} Updated config.json")
|
|
433
|
-
|
|
434
|
-
# Regenerate manifest
|
|
435
|
-
regenerate_manifest(root)
|
|
436
|
-
|
|
437
|
-
print(f"\n{GREEN}✓{NC} Removed {CYAN}{skill_name}{NC}\n")
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# ─── list ────────────────────────────────────────────────────────────────────
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
def cmd_list(args):
|
|
444
|
-
"""List all installed skills with type and trigger count."""
|
|
445
|
-
root = find_project_root()
|
|
446
|
-
skills_dir = os.path.join(root, ".claude", "skills")
|
|
447
|
-
|
|
448
|
-
if not os.path.isdir(skills_dir):
|
|
449
|
-
print(f"\n No skills installed.\n")
|
|
450
|
-
return
|
|
451
|
-
|
|
452
|
-
platform = []
|
|
453
|
-
community = []
|
|
454
|
-
|
|
455
|
-
for entry in sorted(os.listdir(skills_dir)):
|
|
456
|
-
skill_file = os.path.join(skills_dir, entry, "SKILL.md")
|
|
457
|
-
if not os.path.isfile(skill_file):
|
|
458
|
-
continue
|
|
459
|
-
|
|
460
|
-
with open(skill_file, "r", encoding="utf-8") as f:
|
|
461
|
-
content = f.read()
|
|
462
|
-
|
|
463
|
-
fm = parse_frontmatter(content)
|
|
464
|
-
name = fm.get("name", entry) if fm else entry
|
|
465
|
-
triggers = fm.get("triggers", []) if fm else []
|
|
466
|
-
if isinstance(triggers, str):
|
|
467
|
-
triggers = [t.strip() for t in triggers.split(",") if t.strip()]
|
|
468
|
-
|
|
469
|
-
info = {
|
|
470
|
-
"name": name,
|
|
471
|
-
"dir": entry,
|
|
472
|
-
"type": "platform" if is_platform_skill(entry) else "community",
|
|
473
|
-
"triggers": len(triggers),
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if info["type"] == "platform":
|
|
477
|
-
platform.append(info)
|
|
478
|
-
else:
|
|
479
|
-
community.append(info)
|
|
480
|
-
|
|
481
|
-
total = len(platform) + len(community)
|
|
482
|
-
if total == 0:
|
|
483
|
-
print(f"\n No skills installed.\n")
|
|
484
|
-
return
|
|
485
|
-
|
|
486
|
-
print(f"\n {total} skill(s) installed:\n")
|
|
487
|
-
|
|
488
|
-
if platform:
|
|
489
|
-
print(f" {YELLOW}Platform{NC}")
|
|
490
|
-
for s in platform:
|
|
491
|
-
print(f" {CYAN}{s['name']}{NC} ({s['triggers']} triggers)")
|
|
492
|
-
|
|
493
|
-
if community:
|
|
494
|
-
if platform:
|
|
495
|
-
print()
|
|
496
|
-
print(f" {YELLOW}Community{NC}")
|
|
497
|
-
for s in community:
|
|
498
|
-
print(f" {CYAN}{s['name']}{NC} ({s['triggers']} triggers)")
|
|
499
|
-
|
|
500
|
-
print()
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
# ─── main ────────────────────────────────────────────────────────────────────
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
def main():
|
|
507
|
-
parser = argparse.ArgumentParser(
|
|
508
|
-
prog="flydocs skills",
|
|
509
|
-
description="Manage FlyDocs skills — search, install, and remove community skills.",
|
|
510
|
-
)
|
|
511
|
-
sub = parser.add_subparsers(dest="command")
|
|
512
|
-
|
|
513
|
-
search_p = sub.add_parser("search", help="Search available community skills")
|
|
514
|
-
search_p.add_argument("keyword", help="Keyword to search for")
|
|
515
|
-
|
|
516
|
-
add_p = sub.add_parser("add", help="Install a community skill")
|
|
517
|
-
add_p.add_argument("source", help="Skill name, repo:name, or GitHub URL")
|
|
518
|
-
|
|
519
|
-
remove_p = sub.add_parser("remove", help="Remove an installed community skill")
|
|
520
|
-
remove_p.add_argument("name", help="Skill directory name")
|
|
521
|
-
|
|
522
|
-
sub.add_parser("list", help="List installed skills")
|
|
523
|
-
|
|
524
|
-
args = parser.parse_args()
|
|
525
|
-
|
|
526
|
-
if args.command is None:
|
|
527
|
-
parser.print_help()
|
|
528
|
-
sys.exit(1)
|
|
529
|
-
|
|
530
|
-
commands = {
|
|
531
|
-
"search": cmd_search,
|
|
532
|
-
"add": cmd_add,
|
|
533
|
-
"remove": cmd_remove,
|
|
534
|
-
"list": cmd_list,
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
commands[args.command](args)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if __name__ == "__main__":
|
|
541
|
-
main()
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
<!-- AGENT INSTRUCTIONS
|
|
2
|
-
When creating or refining a bug issue:
|
|
3
|
-
|
|
4
|
-
1. CONTEXT SECTION:
|
|
5
|
-
- Capture when/how the bug was discovered
|
|
6
|
-
- Assess user impact (how many affected, how severe)
|
|
7
|
-
- Note frequency (always, sometimes, specific conditions)
|
|
8
|
-
|
|
9
|
-
2. BUG DESCRIPTION:
|
|
10
|
-
- Expected vs Actual must be crystal clear
|
|
11
|
-
- Steps to reproduce must be detailed enough for anyone to follow
|
|
12
|
-
- Include specific data conditions if relevant
|
|
13
|
-
- Note environment details (browser, OS, user role)
|
|
14
|
-
|
|
15
|
-
3. ACCEPTANCE CRITERIA:
|
|
16
|
-
- "Fix Verification" should mirror the reproduction steps
|
|
17
|
-
- Include regression concerns (what else might break)
|
|
18
|
-
- Tests section should include regression test requirement
|
|
19
|
-
|
|
20
|
-
4. TECHNICAL NOTES:
|
|
21
|
-
- Start with hypothesis if cause unknown
|
|
22
|
-
- Update "Confirmed Cause" after investigation
|
|
23
|
-
- Include specific file/function where bug exists
|
|
24
|
-
- Assess risk of the fix
|
|
25
|
-
|
|
26
|
-
5. SEVERITY GUIDANCE:
|
|
27
|
-
- Critical: System unusable, data loss, security issue
|
|
28
|
-
- High: Major feature broken, no workaround
|
|
29
|
-
- Medium: Feature impaired but workaround exists
|
|
30
|
-
- Low: Minor issue, cosmetic, edge case
|
|
31
|
-
|
|
32
|
-
Remove these instructions when creating the final issue.
|
|
33
|
-
-->
|
|
34
|
-
|
|
35
|
-
## Context
|
|
36
|
-
|
|
37
|
-
**When Discovered:** [Date or event when bug was found]
|
|
38
|
-
**Discovered By:** [User, developer, automated test, etc.]
|
|
39
|
-
**Impact:** [How this affects users - be specific about scope]
|
|
40
|
-
**Frequency:** [Always | Sometimes | Rarely | Under specific conditions]
|
|
41
|
-
|
|
42
|
-
[Additional context about the bug]
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Bug Description
|
|
47
|
-
|
|
48
|
-
### Expected Behavior
|
|
49
|
-
[Describe what SHOULD happen - the correct behavior]
|
|
50
|
-
|
|
51
|
-
### Actual Behavior
|
|
52
|
-
[Describe what ACTUALLY happens - the broken behavior]
|
|
53
|
-
|
|
54
|
-
### Steps to Reproduce
|
|
55
|
-
1. [First action - be specific]
|
|
56
|
-
2. [Second action - include any data/conditions needed]
|
|
57
|
-
3. [Third action]
|
|
58
|
-
4. [Observe the bug]
|
|
59
|
-
|
|
60
|
-
### Environment
|
|
61
|
-
- **Browser/Platform:** [Chrome, Safari, Mobile, etc.]
|
|
62
|
-
- **OS:** [macOS, Windows, iOS, Android, etc.]
|
|
63
|
-
- **User Role:** [Which user type experiences this]
|
|
64
|
-
- **Data Conditions:** [Specific data state that triggers bug]
|
|
65
|
-
|
|
66
|
-
### Screenshots/Evidence
|
|
67
|
-
<!-- Add as attachments -->
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Acceptance Criteria
|
|
72
|
-
|
|
73
|
-
### Fix Verification
|
|
74
|
-
- [ ] Bug no longer reproducible using original steps
|
|
75
|
-
- [ ] Expected behavior now works correctly
|
|
76
|
-
- [ ] Fix doesn't break related functionality
|
|
77
|
-
- [ ] No new error messages or console errors
|
|
78
|
-
|
|
79
|
-
### Tests
|
|
80
|
-
- [ ] Regression test added to prevent recurrence
|
|
81
|
-
- [ ] Related edge cases tested
|
|
82
|
-
- [ ] All tests passing
|
|
83
|
-
|
|
84
|
-
### Documentation
|
|
85
|
-
- [ ] Root cause documented (if significant pattern)
|
|
86
|
-
- [ ] Prevention notes added to knowledge base (if applicable)
|
|
87
|
-
- [ ] N/A - No significant documentation needed
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Technical Notes
|
|
92
|
-
|
|
93
|
-
### Root Cause Analysis
|
|
94
|
-
**Hypothesis:** [What you think is causing the bug]
|
|
95
|
-
|
|
96
|
-
**Investigation Findings:**
|
|
97
|
-
- [Finding 1 from debugging]
|
|
98
|
-
- [Finding 2 from debugging]
|
|
99
|
-
|
|
100
|
-
**Confirmed Cause:** [What's actually wrong - file, function, logic error]
|
|
101
|
-
|
|
102
|
-
### Fix Approach
|
|
103
|
-
[Describe the fix strategy - what needs to change]
|
|
104
|
-
|
|
105
|
-
**Files to Modify:**
|
|
106
|
-
- `path/to/file.tsx` - [What needs to change]
|
|
107
|
-
|
|
108
|
-
**Risk Assessment:**
|
|
109
|
-
- **Regression Risk:** Low | Medium | High
|
|
110
|
-
- **Testing Required:** [What needs to be tested]
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Dependencies
|
|
115
|
-
|
|
116
|
-
**Related Systems:**
|
|
117
|
-
- [System or feature where bug occurs]
|
|
118
|
-
|
|
119
|
-
**Blocks:**
|
|
120
|
-
- [Work that can't proceed until bug is fixed]
|
|
121
|
-
- OR: Nothing blocked
|
|
122
|
-
|
|
123
|
-
**Related Bugs:**
|
|
124
|
-
- [Link to related bug if applicable]
|
|
125
|
-
- OR: No related issues
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## AI Effort Estimate
|
|
130
|
-
<!--
|
|
131
|
-
AGENT: Fill this section during /refine using the flydocs-estimates skill.
|
|
132
|
-
See .claude/skills/flydocs-estimates/SKILL.md for calculation details.
|
|
133
|
-
Update Actuals section during /close for calibration.
|
|
134
|
-
-->
|
|
135
|
-
|
|
136
|
-
### Sizing Factors
|
|
137
|
-
|
|
138
|
-
| Factor | Value | Multiplier |
|
|
139
|
-
|--------|-------|------------|
|
|
140
|
-
| **Task Type** | bug | base: 20k |
|
|
141
|
-
| **Scope** | [S/M/L/XL] | ×[0.5/1.0/2.0/4.0] |
|
|
142
|
-
| **Novelty** | [existing/partial/greenfield] | ×[0.7/1.2/2.0] |
|
|
143
|
-
| **Clarity** | [defined/discovery/exploratory] | ×[0.8/1.5/2.5] |
|
|
144
|
-
| **Codebase** | [simple/moderate/complex] | ×[0.8/1.0/1.5] |
|
|
145
|
-
|
|
146
|
-
### Estimate
|
|
147
|
-
|
|
148
|
-
**Provider**: [Claude Sonnet 4]
|
|
149
|
-
**Calculated Tokens**: ~[X]k
|
|
150
|
-
**Confidence**: ±[40-60]%
|
|
151
|
-
**Token Range**: [low]k - [high]k
|
|
152
|
-
**Cost Range**: $[low] - $[high]
|
|
153
|
-
|
|
154
|
-
### Actuals (fill on /close)
|
|
155
|
-
**Actual Tokens**: [fill after completion]
|
|
156
|
-
**Variance**: [+/-X]% [under/over estimate]
|
|
157
|
-
**Notes**: [what drove variance - investigation time, fix complexity, retries]
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
_Reported: YYYY-MM-DD_
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|