@flydocs/cli 0.5.0-beta.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.
Files changed (134) hide show
  1. package/README.md +96 -0
  2. package/dist/cli.js +2666 -0
  3. package/package.json +32 -0
  4. package/template/.claude/CLAUDE.md +90 -0
  5. package/template/.claude/agents/README.md +19 -0
  6. package/template/.claude/agents/implementation-agent.md +29 -0
  7. package/template/.claude/agents/pm-agent.md +29 -0
  8. package/template/.claude/agents/research-agent.md +25 -0
  9. package/template/.claude/agents/review-agent.md +29 -0
  10. package/template/.claude/commands/activate.md +10 -0
  11. package/template/.claude/commands/attach.md +9 -0
  12. package/template/.claude/commands/block.md +10 -0
  13. package/template/.claude/commands/capture.md +10 -0
  14. package/template/.claude/commands/close.md +10 -0
  15. package/template/.claude/commands/flydocs-setup.md +598 -0
  16. package/template/.claude/commands/flydocs-update.md +27 -0
  17. package/template/.claude/commands/implement.md +10 -0
  18. package/template/.claude/commands/new-project.md +11 -0
  19. package/template/.claude/commands/project-update.md +10 -0
  20. package/template/.claude/commands/refine.md +10 -0
  21. package/template/.claude/commands/review.md +10 -0
  22. package/template/.claude/commands/start-session.md +10 -0
  23. package/template/.claude/commands/status.md +10 -0
  24. package/template/.claude/commands/validate.md +10 -0
  25. package/template/.claude/commands/wrap-session.md +10 -0
  26. package/template/.claude/settings.json +49 -0
  27. package/template/.claude/skills/README.md +293 -0
  28. package/template/.claude/skills/flydocs-cloud/SKILL.md +96 -0
  29. package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +50 -0
  30. package/template/.claude/skills/flydocs-cloud/scripts/assign.py +38 -0
  31. package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +44 -0
  32. package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +44 -0
  33. package/template/.claude/skills/flydocs-cloud/scripts/comment.py +39 -0
  34. package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +100 -0
  35. package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +46 -0
  36. package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +40 -0
  37. package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +38 -0
  38. package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +277 -0
  39. package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +77 -0
  40. package/template/.claude/skills/flydocs-cloud/scripts/link.py +47 -0
  41. package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +35 -0
  42. package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +105 -0
  43. package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +40 -0
  44. package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +45 -0
  45. package/template/.claude/skills/flydocs-cloud/scripts/priority.py +38 -0
  46. package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +59 -0
  47. package/template/.claude/skills/flydocs-cloud/scripts/transition.py +67 -0
  48. package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +47 -0
  49. package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +111 -0
  50. package/template/.claude/skills/flydocs-context-graph/SKILL.md +87 -0
  51. package/template/.claude/skills/flydocs-context-graph/schema.md +78 -0
  52. package/template/.claude/skills/flydocs-context-graph/scripts/graph_build.py +299 -0
  53. package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +338 -0
  54. package/template/.claude/skills/flydocs-context-graph/scripts/graph_query.py +191 -0
  55. package/template/.claude/skills/flydocs-context-graph/scripts/graph_session.py +161 -0
  56. package/template/.claude/skills/flydocs-context-graph/scripts/graph_update.py +194 -0
  57. package/template/.claude/skills/flydocs-context-graph/scripts/graph_utils.py +118 -0
  58. package/template/.claude/skills/flydocs-estimates/SKILL.md +384 -0
  59. package/template/.claude/skills/flydocs-estimates/references/provider-costs.md +152 -0
  60. package/template/.claude/skills/flydocs-figma/SKILL.md +377 -0
  61. package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +108 -0
  62. package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +112 -0
  63. package/template/.claude/skills/flydocs-local/SKILL.md +103 -0
  64. package/template/.claude/skills/flydocs-local/cursor-rule.mdc +43 -0
  65. package/template/.claude/skills/flydocs-local/scripts/assign.py +20 -0
  66. package/template/.claude/skills/flydocs-local/scripts/comment.py +27 -0
  67. package/template/.claude/skills/flydocs-local/scripts/create_issue.py +44 -0
  68. package/template/.claude/skills/flydocs-local/scripts/estimate.py +37 -0
  69. package/template/.claude/skills/flydocs-local/scripts/flydocs_api.py +272 -0
  70. package/template/.claude/skills/flydocs-local/scripts/get_issue.py +20 -0
  71. package/template/.claude/skills/flydocs-local/scripts/link.py +41 -0
  72. package/template/.claude/skills/flydocs-local/scripts/list_issues.py +34 -0
  73. package/template/.claude/skills/flydocs-local/scripts/priority.py +37 -0
  74. package/template/.claude/skills/flydocs-local/scripts/project_update.py +67 -0
  75. package/template/.claude/skills/flydocs-local/scripts/status_summary.py +16 -0
  76. package/template/.claude/skills/flydocs-local/scripts/transition.py +24 -0
  77. package/template/.claude/skills/flydocs-local/scripts/update_description.py +35 -0
  78. package/template/.claude/skills/flydocs-local/scripts/update_issue.py +84 -0
  79. package/template/.claude/skills/flydocs-workflow/SKILL.md +85 -0
  80. package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +53 -0
  81. package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +131 -0
  82. package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +76 -0
  83. package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +28 -0
  84. package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +50 -0
  85. package/template/.claude/skills/flydocs-workflow/session.md +128 -0
  86. package/template/.claude/skills/flydocs-workflow/stages/activate.md +46 -0
  87. package/template/.claude/skills/flydocs-workflow/stages/capture.md +50 -0
  88. package/template/.claude/skills/flydocs-workflow/stages/close.md +32 -0
  89. package/template/.claude/skills/flydocs-workflow/stages/implement.md +124 -0
  90. package/template/.claude/skills/flydocs-workflow/stages/refine.md +51 -0
  91. package/template/.claude/skills/flydocs-workflow/stages/review.md +86 -0
  92. package/template/.claude/skills/flydocs-workflow/stages/validate.md +90 -0
  93. package/template/.claude/skills/flydocs-workflow/templates/bug.md +95 -0
  94. package/template/.claude/skills/flydocs-workflow/templates/chore.md +75 -0
  95. package/template/.claude/skills/flydocs-workflow/templates/feature.md +93 -0
  96. package/template/.claude/skills/flydocs-workflow/templates/idea.md +84 -0
  97. package/template/.cursor/agents/implementation-agent.md +28 -0
  98. package/template/.cursor/agents/pm-agent.md +27 -0
  99. package/template/.cursor/agents/research-agent.md +23 -0
  100. package/template/.cursor/agents/review-agent.md +27 -0
  101. package/template/.cursor/hooks.json +29 -0
  102. package/template/.cursor/mcp.json +16 -0
  103. package/template/.env.example +44 -0
  104. package/template/.flydocs/config.json +104 -0
  105. package/template/.flydocs/hooks/auto-approve.py +71 -0
  106. package/template/.flydocs/hooks/post-edit.py +72 -0
  107. package/template/.flydocs/hooks/prefer-scripts.py +89 -0
  108. package/template/.flydocs/hooks/prompt-submit.py +277 -0
  109. package/template/.flydocs/scripts/generate_manifest.py +287 -0
  110. package/template/.flydocs/scripts/skill_manager.py +541 -0
  111. package/template/.flydocs/templates/README.md +46 -0
  112. package/template/.flydocs/templates/bug.md +166 -0
  113. package/template/.flydocs/templates/chore.md +110 -0
  114. package/template/.flydocs/templates/design-system/README.md +27 -0
  115. package/template/.flydocs/templates/design-system/component-patterns.md +92 -0
  116. package/template/.flydocs/templates/design-system/token-mapping.md +168 -0
  117. package/template/.flydocs/templates/feature.md +173 -0
  118. package/template/.flydocs/templates/idea.md +122 -0
  119. package/template/.flydocs/templates/instructions.md +228 -0
  120. package/template/.flydocs/templates/quick-capture.md +35 -0
  121. package/template/.flydocs/templates/scripts/check-design-system.template.mjs +179 -0
  122. package/template/.flydocs/version +1 -0
  123. package/template/AGENTS.md +95 -0
  124. package/template/CHANGELOG.md +271 -0
  125. package/template/flydocs/README.md +186 -0
  126. package/template/flydocs/context/project.md +51 -0
  127. package/template/flydocs/design-system/README.md +126 -0
  128. package/template/flydocs/design-system/component-patterns.md +173 -0
  129. package/template/flydocs/design-system/token-mapping.md +114 -0
  130. package/template/flydocs/knowledge/INDEX.md +100 -0
  131. package/template/flydocs/knowledge/README.md +62 -0
  132. package/template/flydocs/knowledge/product/personas.md +79 -0
  133. package/template/flydocs/knowledge/product/user-flows.md +88 -0
  134. package/template/manifest.json +221 -0
@@ -0,0 +1,541 @@
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()
@@ -0,0 +1,46 @@
1
+ # Issue Templates
2
+
3
+ This folder contains templates for creating and refining Linear issues. Each template includes agent instructions to ensure consistent, high-quality issue structure.
4
+
5
+ ## Template Usage
6
+
7
+ **Agents use these templates when:**
8
+ - Creating new issues via `/capture`
9
+ - Refining issues via `/refine`
10
+ - Triaging quick captures with `triage` label
11
+
12
+ **Humans use `quick-capture.md` in Linear** as the default template for fast idea/bug capture.
13
+
14
+ ## Available Templates
15
+
16
+ | Template | Purpose | When to Use |
17
+ |----------|---------|-------------|
18
+ | `feature.md` | New functionality | Building something new with user story |
19
+ | `bug.md` | Fix defects | Something is broken |
20
+ | `chore.md` | Maintenance/cleanup | Refactoring, polish, improvements |
21
+ | `idea.md` | Capture concepts | Rough ideas for future exploration |
22
+ | `quick-capture.md` | Fast capture | Human entry in Linear (default template) |
23
+
24
+ ## Agent Instructions
25
+
26
+ Each template contains `<!-- AGENT: ... -->` comments that guide the AI when filling out sections. These instructions are removed from the final issue in Linear.
27
+
28
+ **Key principles:**
29
+ 1. Fill all `[bracketed]` sections with specific content
30
+ 2. Don't remove sections - mark as "N/A" if not applicable
31
+ 3. Be specific in acceptance criteria (testable, measurable)
32
+ 4. Include context about "why" not just "what"
33
+ 5. Reference project context from `{content-folder}/context/` when relevant
34
+
35
+ ## Template Evolution
36
+
37
+ Update these templates as your workflow evolves:
38
+ - Add sections that are consistently needed
39
+ - Remove sections that are never used
40
+ - Improve agent instructions based on what works
41
+
42
+ Changes here apply to all future issues.
43
+
44
+
45
+
46
+