@opendirectory.dev/skills 0.1.72 → 0.1.73
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/README.md +19 -0
- package/package.json +6 -1
- package/.claude/skills/claude-md-generator/.env.example +0 -7
- package/.claude/skills/claude-md-generator/README.md +0 -78
- package/.claude/skills/claude-md-generator/SKILL.md +0 -248
- package/.claude/skills/claude-md-generator/evals/evals.json +0 -35
- package/.claude/skills/claude-md-generator/references/section-guide.md +0 -175
- package/src/e2e.test.ts +0 -38
- package/src/fs-adapters.test.ts +0 -91
- package/src/fs-adapters.ts +0 -65
- package/src/index.ts +0 -179
- package/src/transformers.ts +0 -6
- package/tsconfig.json +0 -8
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @opendirectory.dev/skills
|
|
2
|
+
|
|
3
|
+
The official CLI for installing and managing OpenDirectory skills for AI agents (Claude, Cursor, etc.).
|
|
4
|
+
|
|
5
|
+
## Installation & Usage
|
|
6
|
+
|
|
7
|
+
You don't need to install this package globally. You can run it directly using `npx`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx "@opendirectory.dev/skills" install <skill-name> --target claude
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Skills
|
|
14
|
+
|
|
15
|
+
For the full list of available skills, detailed documentation, and contribution guidelines, please visit the [OpenDirectory GitHub Repository](https://github.com/Varnan-Tech/opendirectory).
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendirectory.dev/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"skills",
|
|
9
|
+
"registry.json"
|
|
10
|
+
],
|
|
6
11
|
"bin": {
|
|
7
12
|
"opendirectory": "dist/index.js"
|
|
8
13
|
},
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# claude-md-generator — Environment Variables
|
|
2
|
-
# =============================================
|
|
3
|
-
# Gemini is required for generating the CLAUDE.md content from analysis.
|
|
4
|
-
|
|
5
|
-
# Required: Google Gemini API key for CLAUDE.md generation
|
|
6
|
-
# Get it: aistudio.google.com, Get API key
|
|
7
|
-
GEMINI_API_KEY=your_gemini_api_key_here
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# claude-md-generator
|
|
2
|
-
|
|
3
|
-
<img width="1280" height="640" alt="claude-md-generator" src="https://github.com/user-attachments/assets/0e295271-2216-47f7-828f-845c98ef0298" />
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Reads your codebase and writes a CLAUDE.md that gives Claude Code the context it needs: build commands, code conventions, architecture notes, and gotchas. Stays under 200 lines.
|
|
7
|
-
|
|
8
|
-
## What It Does
|
|
9
|
-
|
|
10
|
-
- Scans project files: package.json, tsconfig.json, linter configs, Makefile, directory structure
|
|
11
|
-
- Extracts all build, test, lint, and dev commands
|
|
12
|
-
- Identifies code style conventions that differ from defaults (path aliases, export patterns, naming)
|
|
13
|
-
- Maps non-obvious architecture decisions
|
|
14
|
-
- Finds gotchas: auto-generated files, required env var setup, test dependencies
|
|
15
|
-
- Generates CLAUDE.md using Gemini, then verifies it stays under 200 lines
|
|
16
|
-
- If CLAUDE.md already exists, improves it without discarding custom content
|
|
17
|
-
|
|
18
|
-
## Requirements
|
|
19
|
-
|
|
20
|
-
| Requirement | Purpose | How to Set Up |
|
|
21
|
-
|------------|---------|--------------|
|
|
22
|
-
| Gemini API key | CLAUDE.md generation from codebase analysis | aistudio.google.com, Get API key |
|
|
23
|
-
|
|
24
|
-
## Setup
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
cp .env.example .env
|
|
28
|
-
# Add GEMINI_API_KEY
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## How to Use
|
|
32
|
-
|
|
33
|
-
From the project root you want to document:
|
|
34
|
-
```
|
|
35
|
-
"Generate a CLAUDE.md for this project"
|
|
36
|
-
"Create a CLAUDE.md"
|
|
37
|
-
"Write Claude configuration for this repo"
|
|
38
|
-
"Help Claude understand this codebase"
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
To update an existing CLAUDE.md:
|
|
42
|
-
```
|
|
43
|
-
"Update my CLAUDE.md: we added Vitest and changed the build system"
|
|
44
|
-
"Improve my existing CLAUDE.md"
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## What Goes in CLAUDE.md
|
|
48
|
-
|
|
49
|
-
| Section | Include | Skip |
|
|
50
|
-
|---------|---------|------|
|
|
51
|
-
| Commands | Exact runnable commands, flags needed, env vars required | `npm install` and other obvious ones |
|
|
52
|
-
| Architecture | Non-obvious structure, auto-generated directories | "src contains source files" |
|
|
53
|
-
| Code Style | Path aliases, export conventions, non-default settings | Indent size (formatter handles it) |
|
|
54
|
-
| Testing | Required setup, how to run one test | "we use Jest" (visible from package.json) |
|
|
55
|
-
| Gotchas | Auto-generated files, env var order, known intentional issues | Things derivable from the code |
|
|
56
|
-
|
|
57
|
-
## Why Under 200 Lines
|
|
58
|
-
|
|
59
|
-
Long CLAUDE.md files get ignored. Claude loads the full file into context every session: a bloated CLAUDE.md with obvious content trains Claude to skim it. A tight 100-150 line CLAUDE.md with only non-obvious facts gets read and used.
|
|
60
|
-
|
|
61
|
-
The skill cuts aggressively: if a section says only things Claude can infer from the code, it removes it.
|
|
62
|
-
|
|
63
|
-
## Project Structure
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
claude-md-generator/
|
|
67
|
-
├── SKILL.md
|
|
68
|
-
├── README.md
|
|
69
|
-
├── .env.example
|
|
70
|
-
├── evals/
|
|
71
|
-
│ └── evals.json
|
|
72
|
-
└── references/
|
|
73
|
-
└── section-guide.md
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## License
|
|
77
|
-
|
|
78
|
-
MIT
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: claude-md-generator
|
|
3
|
-
description: Reads your codebase and writes a CLAUDE.md file that gives Claude Code the context it needs: build commands, test patterns, code style, architecture notes, and gotchas. Supports three modes: create (new file), update (improve existing), audit (score all CLAUDE.md files A-F and report quality). Keeps output under 100 lines. Use when asked to create or improve CLAUDE.md, audit Claude configuration, write AI coding context, or set up Claude for a new project. Trigger when a user says "create a CLAUDE.md", "generate CLAUDE.md", "audit my CLAUDE.md", "help Claude understand my project", or "set up Claude for this repo".
|
|
4
|
-
compatibility: [claude-code, gemini-cli, github-copilot]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# CLAUDE.md Generator
|
|
8
|
-
|
|
9
|
-
Read the codebase. Write a CLAUDE.md that tells Claude exactly what it needs: no more, no less.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
**Critical rule:** A good CLAUDE.md is under 100 lines. It contains only information Claude cannot derive from reading the code itself. Do not auto-write the file: always show the draft and wait for user approval first.
|
|
14
|
-
|
|
15
|
-
**Code snippet rule:** Never include inline code examples in CLAUDE.md. Instead use `file.ts:42` references. Code in CLAUDE.md wastes tokens and goes stale.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Step 1: Detect Mode
|
|
20
|
-
|
|
21
|
-
Determine which of three modes to run:
|
|
22
|
-
|
|
23
|
-
**create**: No CLAUDE.md exists. Write one from scratch.
|
|
24
|
-
**update**: A CLAUDE.md exists. Improve it without discarding custom content.
|
|
25
|
-
**audit**: Score all CLAUDE.md files in the project A-F and output a quality report. If the user says "audit", "check", "review", or "grade" my CLAUDE.md, run audit mode.
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
# Discover ALL CLAUDE.md locations
|
|
29
|
-
find . -name "CLAUDE.md" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null
|
|
30
|
-
ls ~/.claude/CLAUDE.md 2>/dev/null && echo "Global CLAUDE.md found"
|
|
31
|
-
ls .claude.local.md 2>/dev/null && echo ".claude.local.md found"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
If multiple CLAUDE.md files are found: list them. Ask: "Found CLAUDE.md in [locations]. Should I update all of them or just [root]?"
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Step 2: Audit Mode (skip to Step 3 if create/update)
|
|
39
|
-
|
|
40
|
-
For each CLAUDE.md found, score it A-F using this rubric:
|
|
41
|
-
|
|
42
|
-
| Criterion | What to check |
|
|
43
|
-
|-----------|--------------|
|
|
44
|
-
| Commands | Build/test/lint commands present and runnable? |
|
|
45
|
-
| Architecture | Non-obvious structure explained? |
|
|
46
|
-
| Non-obvious patterns | Gotchas, generated files, env var order documented? |
|
|
47
|
-
| Conciseness | Under 100 lines? No obvious filler? |
|
|
48
|
-
| Currency | Commands still match current package.json/Makefile? |
|
|
49
|
-
| Actionability | Can a new contributor follow this without asking questions? |
|
|
50
|
-
|
|
51
|
-
Score: 90-100 = A, 70-89 = B, 50-69 = C, 30-49 = D, 0-29 = F
|
|
52
|
-
|
|
53
|
-
Present as a table:
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
## CLAUDE.md Audit Report
|
|
57
|
-
|
|
58
|
-
| File | Score | Grade | Top Issues |
|
|
59
|
-
|------|-------|-------|-----------|
|
|
60
|
-
| ./CLAUDE.md | 72 | B | Missing gotchas section, test command outdated |
|
|
61
|
-
| ./packages/api/CLAUDE.md | 45 | D | No commands, 340 lines (too long), stale arch notes |
|
|
62
|
-
|
|
63
|
-
**Overall: B (72/100)**
|
|
64
|
-
|
|
65
|
-
Issues found:
|
|
66
|
-
- ./packages/api/CLAUDE.md: 340 lines: well over the 100-line target
|
|
67
|
-
- ./packages/api/CLAUDE.md: Test command references `jest` but package.json uses `vitest`
|
|
68
|
-
- ./CLAUDE.md: No Gotchas section: most valuable section is missing
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
After the report, ask: "Want me to fix any of these? (all / just root / specify)"
|
|
72
|
-
|
|
73
|
-
If user says yes, continue to Step 3 for each file they want fixed.
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Step 3: Scan Project Structure
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
# Project type and package manager
|
|
81
|
-
ls package.json yarn.lock pnpm-lock.yaml bun.lockb requirements.txt pyproject.toml Cargo.toml go.mod 2>/dev/null
|
|
82
|
-
|
|
83
|
-
# Top-level directory structure
|
|
84
|
-
find . -maxdepth 2 -type d \
|
|
85
|
-
| grep -v node_modules | grep -v .git | grep -v __pycache__ \
|
|
86
|
-
| grep -v ".next" | grep -v dist | grep -v build | sort
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Step 4: Extract Build and Test Commands
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
# npm/yarn/pnpm/bun scripts
|
|
95
|
-
cat package.json 2>/dev/null \
|
|
96
|
-
| python3 -c "
|
|
97
|
-
import sys, json
|
|
98
|
-
d = json.load(sys.stdin)
|
|
99
|
-
for name, cmd in d.get('scripts', {}).items():
|
|
100
|
-
print(f'{name}: {cmd}')
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
# Python, Go, Rust Makefiles
|
|
104
|
-
cat Makefile 2>/dev/null | grep -E "^[a-z].*:" | head -20
|
|
105
|
-
|
|
106
|
-
# Go
|
|
107
|
-
cat go.mod 2>/dev/null | head -5
|
|
108
|
-
|
|
109
|
-
# Rust
|
|
110
|
-
cat Cargo.toml 2>/dev/null | grep -E "^\[" | head -10
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Identify the exact commands for: build, test (all), test (single file/name), dev server, lint/typecheck. Note any env vars required to run them.
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Step 5: Find Code Style and Gotchas
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
# Import aliases (most commonly missed)
|
|
121
|
-
python3 -c "
|
|
122
|
-
import json, sys
|
|
123
|
-
try:
|
|
124
|
-
d = json.load(open('tsconfig.json'))
|
|
125
|
-
paths = d.get('compilerOptions', {}).get('paths', {})
|
|
126
|
-
if paths: print('Import aliases:', json.dumps(paths, indent=2))
|
|
127
|
-
except: pass
|
|
128
|
-
" 2>/dev/null
|
|
129
|
-
|
|
130
|
-
# Environment variables required
|
|
131
|
-
cat .env.example 2>/dev/null | grep -v "^#" | grep -v "^$" | head -20
|
|
132
|
-
|
|
133
|
-
# Auto-generated files (must not be edited)
|
|
134
|
-
find . -path "*/node_modules" -prune -o -name "*.ts" -print \
|
|
135
|
-
| xargs grep -l "DO NOT EDIT\|@generated\|Generated by" 2>/dev/null | head -5
|
|
136
|
-
|
|
137
|
-
# Test setup requirements
|
|
138
|
-
cat jest.config.js jest.config.ts vitest.config.ts 2>/dev/null | head -30
|
|
139
|
-
|
|
140
|
-
# Database/migration setup
|
|
141
|
-
ls migrations/ prisma/ drizzle/ db/ 2>/dev/null
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
**What counts as a Gotcha** (include these, skip everything else):
|
|
145
|
-
- Files that are auto-generated (must not edit)
|
|
146
|
-
- Env vars required BEFORE tests run
|
|
147
|
-
- Non-default import alias mappings
|
|
148
|
-
- Test commands that require a running service
|
|
149
|
-
- Known intentional quirks (workarounds, not bugs)
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Step 6: Generate CLAUDE.md Draft with Gemini
|
|
154
|
-
|
|
155
|
-
Compile all findings and generate the draft:
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
cat > /tmp/claude-md-request.json << 'ENDJSON'
|
|
159
|
-
{
|
|
160
|
-
"system_instruction": {
|
|
161
|
-
"parts": [{
|
|
162
|
-
"text": "Write a CLAUDE.md file for a software project. Rules: (1) Under 100 lines total. (2) Only include what Claude cannot derive from reading the code. (3) No inline code examples: use file.ts:42 references instead. (4) Sections: Commands, Code Style (only non-defaults), Testing (only if setup needed), Gotchas (required: what trips people up). Skip any section that has nothing non-obvious to say. (5) All commands in code blocks. (6) Preferred order: short Project Overview (1-2 sentences, only if non-obvious), Commands, Architecture (only non-obvious structure), Code Style, Testing, Gotchas. (7) Do not use em dashes. (8) Output only the CLAUDE.md content, no commentary."
|
|
163
|
-
}]
|
|
164
|
-
},
|
|
165
|
-
"contents": [{
|
|
166
|
-
"parts": [{
|
|
167
|
-
"text": "PROJECT_ANALYSIS_HERE"
|
|
168
|
-
}]
|
|
169
|
-
}],
|
|
170
|
-
"generationConfig": {
|
|
171
|
-
"temperature": 0.3,
|
|
172
|
-
"maxOutputTokens": 2048
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
ENDJSON
|
|
176
|
-
|
|
177
|
-
curl -s -X POST \
|
|
178
|
-
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
|
|
179
|
-
-H "Content-Type: application/json" \
|
|
180
|
-
-d @/tmp/claude-md-request.json \
|
|
181
|
-
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['candidates'][0]['content']['parts'][0]['text'])"
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Replace `PROJECT_ANALYSIS_HERE` with findings from Steps 3-5.
|
|
185
|
-
|
|
186
|
-
**For large projects (50+ files):** Add a `@path` pointer at the bottom of CLAUDE.md instead of inline detail:
|
|
187
|
-
|
|
188
|
-
```markdown
|
|
189
|
-
## Extended Reference
|
|
190
|
-
See @docs/ai-context/architecture.md for full module map.
|
|
191
|
-
See @docs/ai-context/testing.md for integration test setup details.
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Write the referenced files to `docs/ai-context/` with the detail that would not fit in 100 lines.
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Step 7: Self-QA
|
|
199
|
-
|
|
200
|
-
Before presenting the draft, check:
|
|
201
|
-
|
|
202
|
-
- [ ] Under 100 lines (count: `echo "$CONTENT" | wc -l`)
|
|
203
|
-
- [ ] No inline code examples (only `file.ts:42` references or shell commands)
|
|
204
|
-
- [ ] All commands in code blocks and runnable as-is
|
|
205
|
-
- [ ] Gotchas section present with at least one real entry
|
|
206
|
-
- [ ] No section that says only things obvious from the files
|
|
207
|
-
- [ ] No em dashes
|
|
208
|
-
- [ ] No marketing words or filler phrases ("This project uses React to...")
|
|
209
|
-
- [ ] Import aliases documented if they exist
|
|
210
|
-
- [ ] Auto-generated files marked "do not edit" if they exist
|
|
211
|
-
|
|
212
|
-
If any check fails, revise before presenting.
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Step 8: Present Draft and Wait for Approval
|
|
217
|
-
|
|
218
|
-
**Never write the file without user approval.**
|
|
219
|
-
|
|
220
|
-
Present the draft in a code block:
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
## Draft CLAUDE.md ([N] lines)
|
|
224
|
-
|
|
225
|
-
[full draft content here]
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
Write this to CLAUDE.md? (yes / edit first / cancel)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
If user says **yes**: write the file, then confirm:
|
|
232
|
-
"CLAUDE.md written ([N] lines). Sections: [list of ## headers]."
|
|
233
|
-
|
|
234
|
-
If user says **edit first**: apply their edits, re-show the draft.
|
|
235
|
-
|
|
236
|
-
If user says **cancel**: stop.
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## What NOT to Include
|
|
241
|
-
|
|
242
|
-
- Language/framework version ("This is a TypeScript project")
|
|
243
|
-
- How the framework works (Claude already knows React, FastAPI, etc.)
|
|
244
|
-
- List of all dependencies
|
|
245
|
-
- Style rules the linter already enforces (indent size, quote style)
|
|
246
|
-
- Content that duplicates README.md
|
|
247
|
-
- Inline code examples or multi-line snippets
|
|
248
|
-
- Anything that would be identical for any project using the same stack
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"skill_name": "claude-md-generator",
|
|
3
|
-
"evals": [
|
|
4
|
-
{
|
|
5
|
-
"id": 1,
|
|
6
|
-
"prompt": "Generate a CLAUDE.md for this project.",
|
|
7
|
-
"expected_output": "Agent runs all scan commands from Steps 1-5: directory structure, package.json scripts, tsconfig.json, ESLint config, test setup, .env.example. Calls Gemini with analysis to generate CLAUDE.md. Output is under 200 lines. Contains: Commands section with exact runnable commands, Code Style section noting any non-default aliases or export conventions, Testing section with any required setup, Gotchas section with at least one entry (auto-generated files, required env vars, etc.). Does not include obvious facts derivable from the files (e.g., 'This project uses TypeScript').",
|
|
8
|
-
"files": ["package.json", "tsconfig.json", ".eslintrc.json", ".env.example"]
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"id": 2,
|
|
12
|
-
"prompt": "Create a CLAUDE.md. The project already has one.",
|
|
13
|
-
"expected_output": "Agent detects existing CLAUDE.md at project root. Reads it before generating. Preserves any sections the user has manually added. Improves sections that are outdated or missing. Shows a diff summary: 'Kept: [sections]. Updated: [sections]. Added: [sections].' Final file is still under 200 lines. Does not wholesale replace the existing file without showing what changed.",
|
|
14
|
-
"files": ["CLAUDE.md", "package.json"]
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"id": 3,
|
|
18
|
-
"prompt": "Write a CLAUDE.md for this Python project.",
|
|
19
|
-
"expected_output": "Agent detects Python project from requirements.txt or pyproject.toml. Reads Makefile or pyproject.toml for commands. Identifies test runner (pytest, unittest). Notes any virtual environment setup required. Checks for generated files (migrations, protobuf stubs). Commands section shows exact Python commands (pytest, python -m, etc.). Gotchas section notes any required env vars and any generated files that should not be edited.",
|
|
20
|
-
"files": ["requirements.txt", "pyproject.toml", "Makefile"]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": 4,
|
|
24
|
-
"prompt": "Generate CLAUDE.md for this monorepo.",
|
|
25
|
-
"expected_output": "Agent detects monorepo from presence of packages/ or apps/ directory with multiple package.json files. Notes workspace structure in Architecture section. Documents root-level commands vs package-level commands. Notes any shared packages and how they are linked. Keeps the output under 200 lines by focusing on the most important commands and conventions. Does not try to document every package: focuses on the top-level patterns and the most commonly developed packages.",
|
|
26
|
-
"files": ["package.json", "packages/"]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"id": 5,
|
|
30
|
-
"prompt": "Create a CLAUDE.md. There are no config files: it's a small Go project with just main.go and a Makefile.",
|
|
31
|
-
"expected_output": "Agent finds main.go and Makefile. Reads Makefile for build/test/run targets. Notes that there is no package manager. Commands section shows make targets. Architecture section notes entry point (main.go). Gotchas section notes any env vars referenced in main.go. Output is short (under 50 lines) because there is not much to document: the agent does not pad with filler content. Does not invent sections for things that do not exist in this project.",
|
|
32
|
-
"files": ["main.go", "Makefile"]
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md Section Guide
|
|
2
|
-
|
|
3
|
-
What to include in each section and what to leave out.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Canonical Section Order
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
# CLAUDE.md
|
|
11
|
-
|
|
12
|
-
## Project Overview
|
|
13
|
-
## Commands
|
|
14
|
-
## Architecture
|
|
15
|
-
## Code Style
|
|
16
|
-
## Testing
|
|
17
|
-
## Gotchas
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Not all sections are required. If a section has nothing non-obvious to say, omit it.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Project Overview
|
|
25
|
-
|
|
26
|
-
**Include:**
|
|
27
|
-
- What the project does in one sentence (only if it is not obvious from the directory name)
|
|
28
|
-
- The core tech stack if it is unusual or non-standard for the file types present
|
|
29
|
-
- Any important context for understanding the codebase (e.g., "this is the billing service, not the main app")
|
|
30
|
-
|
|
31
|
-
**Do not include:**
|
|
32
|
-
- "This is a Next.js project" (Claude can see the files)
|
|
33
|
-
- Marketing copy about what the product does for users
|
|
34
|
-
- History of the project or team
|
|
35
|
-
|
|
36
|
-
**Example: good:**
|
|
37
|
-
```markdown
|
|
38
|
-
## Project Overview
|
|
39
|
-
The billing service. Handles subscription lifecycle, invoices, and Stripe webhooks. Runs as a standalone Express app; the main app in /apps/web calls it via internal API.
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Commands
|
|
45
|
-
|
|
46
|
-
**Include:**
|
|
47
|
-
- The exact command to run the dev server
|
|
48
|
-
- The exact command to run all tests
|
|
49
|
-
- How to run a single test file or test by name
|
|
50
|
-
- How to build for production
|
|
51
|
-
- Lint/typecheck command
|
|
52
|
-
- Any command that requires setup steps to work (note the setup)
|
|
53
|
-
- Database migration commands if they are needed before running tests
|
|
54
|
-
|
|
55
|
-
**Do not include:**
|
|
56
|
-
- Commands that are self-explanatory from package.json (`npm install`, `npm start`)
|
|
57
|
-
- Commands that Claude can infer from the framework
|
|
58
|
-
|
|
59
|
-
**Example: good:**
|
|
60
|
-
```markdown
|
|
61
|
-
## Commands
|
|
62
|
-
- Dev: `npm run dev` (starts on port 3000)
|
|
63
|
-
- Test: `npm test` (requires `DATABASE_URL` in .env.local)
|
|
64
|
-
- Test single file: `npm test -- --testPathPattern=auth`
|
|
65
|
-
- Lint: `npm run lint`
|
|
66
|
-
- Build: `npm run build && npm run export`
|
|
67
|
-
- DB migrations: `npm run db:migrate` (run after pulling main)
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Architecture
|
|
73
|
-
|
|
74
|
-
**Include:**
|
|
75
|
-
- Non-obvious directory organization (e.g., why `lib/` vs `utils/` exists)
|
|
76
|
-
- How the main entry points connect to each other
|
|
77
|
-
- External services and what they are used for
|
|
78
|
-
- Any generated directories that should not be edited
|
|
79
|
-
|
|
80
|
-
**Do not include:**
|
|
81
|
-
- "The src directory contains source code" (obvious)
|
|
82
|
-
- Framework defaults ("pages directory is for Next.js pages")
|
|
83
|
-
- Descriptions of standard patterns (REST API routes, MVC structure)
|
|
84
|
-
|
|
85
|
-
**Example: good:**
|
|
86
|
-
```markdown
|
|
87
|
-
## Architecture
|
|
88
|
-
`src/api/`: Express route handlers only. Business logic lives in `src/services/`.
|
|
89
|
-
`src/generated/`: Auto-generated from Prisma schema and GraphQL introspection. Do not edit these files.
|
|
90
|
-
The queue workers (`src/workers/`) are separate processes; they share the database but do not import from `src/api/`.
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Code Style
|
|
96
|
-
|
|
97
|
-
**Include:**
|
|
98
|
-
- Anything that differs from the linter/formatter default
|
|
99
|
-
- Import alias mappings (`@/` = `src/`, `~components/` = `src/components/`)
|
|
100
|
-
- Export convention (named vs default) if the project enforces one consistently
|
|
101
|
-
- File naming convention if it differs from framework default
|
|
102
|
-
- Any rule about where to put types
|
|
103
|
-
|
|
104
|
-
**Do not include:**
|
|
105
|
-
- Indent size and tab/space settings (the formatter enforces this)
|
|
106
|
-
- "We use TypeScript" (obviously visible from the files)
|
|
107
|
-
- ESLint rules that are already in .eslintrc
|
|
108
|
-
|
|
109
|
-
**Example: good:**
|
|
110
|
-
```markdown
|
|
111
|
-
## Code Style
|
|
112
|
-
- Imports: use `@/` alias for `src/` (configured in tsconfig paths and Jest moduleNameMapper)
|
|
113
|
-
- Exports: named exports only: no default exports except for Next.js pages
|
|
114
|
-
- Types: co-located with the code that uses them; shared types in `src/types/`
|
|
115
|
-
- Components: one component per file, file name matches component name
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Testing
|
|
121
|
-
|
|
122
|
-
**Include:**
|
|
123
|
-
- How to run tests (if not already in Commands)
|
|
124
|
-
- What needs to be running for tests to pass (database, mock server, env vars)
|
|
125
|
-
- Test file naming convention if non-standard
|
|
126
|
-
- Where fixtures or test data live
|
|
127
|
-
- Any `beforeAll` setup that is important to know about
|
|
128
|
-
|
|
129
|
-
**Do not include:**
|
|
130
|
-
- "We use Jest" (visible from package.json)
|
|
131
|
-
- "Tests go in the tests directory" (obvious from the file structure)
|
|
132
|
-
|
|
133
|
-
**Example: good:**
|
|
134
|
-
```markdown
|
|
135
|
-
## Testing
|
|
136
|
-
Tests require a running PostgreSQL instance. Start it with `docker compose up -d db` before running `npm test`.
|
|
137
|
-
Test files: `*.test.ts` next to the source file. Integration tests: `tests/integration/*.test.ts`.
|
|
138
|
-
Fixtures: `tests/fixtures/`: seeded before each test suite in `tests/setup.ts`.
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Gotchas
|
|
144
|
-
|
|
145
|
-
**The most important section.** This is where you put the things that will waste 30+ minutes if not documented.
|
|
146
|
-
|
|
147
|
-
**Always include:**
|
|
148
|
-
- Things that look like they should work but do not
|
|
149
|
-
- Files that are auto-generated and should not be edited manually
|
|
150
|
-
- Env vars that must exist before the app starts
|
|
151
|
-
- Dependencies between services (e.g., must start service A before service B)
|
|
152
|
-
- Known issues that are intentional (not bugs)
|
|
153
|
-
- Auth setup that differs from standard
|
|
154
|
-
|
|
155
|
-
**Examples:**
|
|
156
|
-
|
|
157
|
-
```markdown
|
|
158
|
-
## Gotchas
|
|
159
|
-
- `src/graphql/types.ts` is auto-generated by `npm run codegen`. Do not edit it directly.
|
|
160
|
-
- The test database is separate from the dev database. Run `npm run db:seed:test` once before running tests for the first time.
|
|
161
|
-
- `NEXT_PUBLIC_API_URL` must be set at build time (not runtime): changing it requires a rebuild.
|
|
162
|
-
- The `useAuth` hook returns `null` during SSR. Guard with `if (!user)` before accessing user properties.
|
|
163
|
-
- Tailwind classes are not purged during dev but are in production. If a class works in dev but disappears in production, check that it appears as a complete string (not assembled dynamically).
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## What CLAUDE.md is NOT
|
|
169
|
-
|
|
170
|
-
- Not a README for human developers
|
|
171
|
-
- Not documentation of how the framework works
|
|
172
|
-
- Not a list of all dependencies
|
|
173
|
-
- Not a tutorial for new team members
|
|
174
|
-
|
|
175
|
-
If a section would read the same for any project using the same framework, cut it.
|
package/src/e2e.test.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import * as os from 'node:os';
|
|
6
|
-
|
|
7
|
-
describe('CLI End-to-End Tests', () => {
|
|
8
|
-
let tempDir: string;
|
|
9
|
-
let cliPath: string;
|
|
10
|
-
|
|
11
|
-
beforeAll(() => {
|
|
12
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opendirectory-cli-test-'));
|
|
13
|
-
cliPath = path.resolve(__dirname, '../dist/index.js');
|
|
14
|
-
|
|
15
|
-
if (!fs.existsSync(cliPath)) {
|
|
16
|
-
execSync('pnpm run build', { cwd: path.resolve(__dirname, '..') });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterAll(() => {
|
|
21
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should install a skill for opencode locally', () => {
|
|
25
|
-
const skillName = 'claude-md-generator';
|
|
26
|
-
|
|
27
|
-
// Set HOME and USERPROFILE to tempDir so that ~ resolves to tempDir
|
|
28
|
-
const env = { ...process.env, HOME: tempDir, USERPROFILE: tempDir };
|
|
29
|
-
|
|
30
|
-
execSync(`node "${cliPath}" install ${skillName} --target opencode`, { cwd: tempDir, env });
|
|
31
|
-
|
|
32
|
-
const expectedPath = path.join(tempDir, '.config', 'opencode', 'skills', skillName, 'SKILL.md');
|
|
33
|
-
expect(fs.existsSync(expectedPath)).toBe(true);
|
|
34
|
-
|
|
35
|
-
const content = fs.readFileSync(expectedPath, 'utf-8');
|
|
36
|
-
expect(content.length).toBeGreaterThan(0);
|
|
37
|
-
});
|
|
38
|
-
});
|
package/src/fs-adapters.test.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import * as fs from 'node:fs/promises';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import * as os from 'node:os';
|
|
5
|
-
import { resolvePath, safeWriteFile, safeAppendFile } from './fs-adapters';
|
|
6
|
-
|
|
7
|
-
describe('fs-adapters', () => {
|
|
8
|
-
let tempDir: string;
|
|
9
|
-
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fs-adapters-test-'));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('resolvePath', () => {
|
|
19
|
-
it('should resolve ~ to home directory', () => {
|
|
20
|
-
const home = os.homedir();
|
|
21
|
-
expect(resolvePath('~/foo/bar')).toBe(path.join(home, 'foo/bar'));
|
|
22
|
-
expect(resolvePath('~')).toBe(home);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should resolve absolute paths', () => {
|
|
26
|
-
const absPath = path.resolve('/foo/bar');
|
|
27
|
-
expect(resolvePath('/foo/bar')).toBe(absPath);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should resolve relative paths', () => {
|
|
31
|
-
const relPath = path.resolve('foo/bar');
|
|
32
|
-
expect(resolvePath('foo/bar')).toBe(relPath);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe('safeWriteFile', () => {
|
|
37
|
-
it('should create directory and write file', async () => {
|
|
38
|
-
const filePath = path.join(tempDir, 'nested', 'dir', 'test.txt');
|
|
39
|
-
await safeWriteFile(filePath, 'hello world');
|
|
40
|
-
|
|
41
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
42
|
-
expect(content).toBe('hello world');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should overwrite existing file', async () => {
|
|
46
|
-
const filePath = path.join(tempDir, 'test.txt');
|
|
47
|
-
await safeWriteFile(filePath, 'hello world');
|
|
48
|
-
await safeWriteFile(filePath, 'new content');
|
|
49
|
-
|
|
50
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
51
|
-
expect(content).toBe('new content');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('safeAppendFile', () => {
|
|
56
|
-
it('should create file if it does not exist', async () => {
|
|
57
|
-
const filePath = path.join(tempDir, 'nested', 'test.txt');
|
|
58
|
-
await safeAppendFile(filePath, 'hello world');
|
|
59
|
-
|
|
60
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
61
|
-
expect(content).toBe('hello world');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should append to existing file', async () => {
|
|
65
|
-
const filePath = path.join(tempDir, 'test.txt');
|
|
66
|
-
await safeWriteFile(filePath, 'line 1\n');
|
|
67
|
-
await safeAppendFile(filePath, 'line 2');
|
|
68
|
-
|
|
69
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
70
|
-
expect(content).toBe('line 1\nline 2');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should add newline if existing file does not end with one', async () => {
|
|
74
|
-
const filePath = path.join(tempDir, 'test.txt');
|
|
75
|
-
await safeWriteFile(filePath, 'line 1');
|
|
76
|
-
await safeAppendFile(filePath, 'line 2');
|
|
77
|
-
|
|
78
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
79
|
-
expect(content).toBe('line 1\nline 2');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should not append if content already exists', async () => {
|
|
83
|
-
const filePath = path.join(tempDir, 'test.txt');
|
|
84
|
-
await safeWriteFile(filePath, 'existing content\nmore stuff');
|
|
85
|
-
await safeAppendFile(filePath, 'existing content');
|
|
86
|
-
|
|
87
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
88
|
-
expect(content).toBe('existing content\nmore stuff');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
});
|
package/src/fs-adapters.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
|
|
5
|
-
export function resolvePath(p: string): string {
|
|
6
|
-
if (p.startsWith('~/') || p === '~') {
|
|
7
|
-
return path.resolve(p.replace(/^~/, os.homedir()));
|
|
8
|
-
}
|
|
9
|
-
return path.resolve(p);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function safeWriteFile(filePath: string, content: string): Promise<void> {
|
|
13
|
-
const resolvedPath = resolvePath(filePath);
|
|
14
|
-
const dir = path.dirname(resolvedPath);
|
|
15
|
-
await fs.mkdir(dir, { recursive: true });
|
|
16
|
-
await fs.writeFile(resolvedPath, content, 'utf-8');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function safeAppendFile(filePath: string, content: string): Promise<void> {
|
|
20
|
-
const resolvedPath = resolvePath(filePath);
|
|
21
|
-
const dir = path.dirname(resolvedPath);
|
|
22
|
-
await fs.mkdir(dir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const existingContent = await fs.readFile(resolvedPath, 'utf-8');
|
|
26
|
-
if (existingContent.includes(content)) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const prefix = existingContent.length > 0 && !existingContent.endsWith('\n') ? '\n' : '';
|
|
31
|
-
await fs.appendFile(resolvedPath, prefix + content, 'utf-8');
|
|
32
|
-
} catch (error: any) {
|
|
33
|
-
if (error.code === 'ENOENT') {
|
|
34
|
-
await fs.writeFile(resolvedPath, content, 'utf-8');
|
|
35
|
-
} else {
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function updateHermesConfig(): Promise<void> {
|
|
42
|
-
const configPath = resolvePath('~/.hermes/config.yaml');
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
let content = await fs.readFile(configPath, 'utf-8');
|
|
46
|
-
|
|
47
|
-
if (!content.includes('skills:')) {
|
|
48
|
-
const prefix = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
|
|
49
|
-
content += prefix + 'skills:\n external_dirs:\n - "./.hermes/skills"\n';
|
|
50
|
-
} else if (!content.includes('external_dirs:')) {
|
|
51
|
-
content = content.replace(/(skills:\s*\n)/, '$1 external_dirs:\n - "./.hermes/skills"\n');
|
|
52
|
-
} else if (!content.includes('./.hermes/skills')) {
|
|
53
|
-
content = content.replace(/(external_dirs:\s*\n)/, '$1 - "./.hermes/skills"\n');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await fs.writeFile(configPath, content, 'utf-8');
|
|
57
|
-
} catch (error: any) {
|
|
58
|
-
if (error.code === 'ENOENT') {
|
|
59
|
-
const initialContent = `skills:\n external_dirs:\n - "./.hermes/skills"\n`;
|
|
60
|
-
await safeWriteFile('~/.hermes/config.yaml', initialContent);
|
|
61
|
-
} else {
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import * as fs from 'node:fs/promises';
|
|
5
|
-
import * as fsSync from 'node:fs';
|
|
6
|
-
import * as path from 'node:path';
|
|
7
|
-
import { Skill } from './transformers';
|
|
8
|
-
import { safeWriteFile } from './fs-adapters';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import ora from 'ora';
|
|
11
|
-
import Table from 'cli-table3';
|
|
12
|
-
|
|
13
|
-
const program = new Command();
|
|
14
|
-
|
|
15
|
-
const pkg = JSON.parse(fsSync.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
16
|
-
|
|
17
|
-
program
|
|
18
|
-
.name('@opendirectory.dev/skills')
|
|
19
|
-
.description(chalk.blue.bold('CLI to install OpenDirectory skills'))
|
|
20
|
-
.version(pkg.version);
|
|
21
|
-
|
|
22
|
-
const getProjectRoot = () => {
|
|
23
|
-
return path.resolve(__dirname, '..');
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
program
|
|
27
|
-
.command('list')
|
|
28
|
-
.description('List available skills in the Open Directory registry')
|
|
29
|
-
.action(async () => {
|
|
30
|
-
const spinner = ora('Fetching available skills...').start();
|
|
31
|
-
try {
|
|
32
|
-
const root = getProjectRoot();
|
|
33
|
-
const registryPath = path.join(root, 'registry.json');
|
|
34
|
-
|
|
35
|
-
let skills: any[] = [];
|
|
36
|
-
try {
|
|
37
|
-
const registryContent = await fs.readFile(registryPath, 'utf-8');
|
|
38
|
-
skills = JSON.parse(registryContent);
|
|
39
|
-
} catch (e) {
|
|
40
|
-
const skillsDir = path.join(root, 'skills');
|
|
41
|
-
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
42
|
-
skills = entries
|
|
43
|
-
.filter(entry => entry.isDirectory())
|
|
44
|
-
.map(entry => ({ name: entry.name, description: `Skill: ${entry.name}` }));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
spinner.stop();
|
|
48
|
-
console.log(chalk.green('Successfully loaded Open Directory registry!\n'));
|
|
49
|
-
|
|
50
|
-
const table = new Table({
|
|
51
|
-
head: [chalk.cyan.bold('Skill Name'), chalk.cyan.bold('Description')],
|
|
52
|
-
colWidths: [35, 75],
|
|
53
|
-
wordWrap: true
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
for (const skill of skills) {
|
|
57
|
-
let desc = skill.description || '';
|
|
58
|
-
desc = desc.replace(/<img[^>]*>/g, '').trim();
|
|
59
|
-
if (desc.length > 100) desc = desc.substring(0, 97) + '...';
|
|
60
|
-
|
|
61
|
-
table.push([chalk.yellow(skill.name), desc]);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
console.log(table.toString());
|
|
65
|
-
console.log(chalk.gray(`\nRun \`${chalk.white('npx "@opendirectory.dev/skills" install <skill-name> --target <agent>')}\` to install a skill.`));
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
spinner.stop();
|
|
69
|
-
console.error(chalk.red('Failed to list skills.'));
|
|
70
|
-
console.error(error);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
program
|
|
75
|
-
.command('install <skill>')
|
|
76
|
-
.description('Install a skill for your AI agent')
|
|
77
|
-
.requiredOption('-t, --target <tool>', 'Target agent (opencode, claude, codex, gemini, anti-gravity, openclaw, hermes)')
|
|
78
|
-
.action(async (skillName, options) => {
|
|
79
|
-
const spinner = ora(`Installing ${chalk.yellow(skillName)}...`).start();
|
|
80
|
-
try {
|
|
81
|
-
const root = getProjectRoot();
|
|
82
|
-
const repoDir = path.join(root, 'skills', skillName);
|
|
83
|
-
|
|
84
|
-
let skillDir = repoDir;
|
|
85
|
-
let skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
await fs.access(skillMdPath);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
try {
|
|
91
|
-
const entries = await fs.readdir(repoDir, { withFileTypes: true });
|
|
92
|
-
for (const entry of entries) {
|
|
93
|
-
if (entry.isDirectory()) {
|
|
94
|
-
const possiblePath = path.join(repoDir, entry.name, 'SKILL.md');
|
|
95
|
-
try {
|
|
96
|
-
await fs.access(possiblePath);
|
|
97
|
-
skillDir = path.join(repoDir, entry.name);
|
|
98
|
-
skillMdPath = possiblePath;
|
|
99
|
-
break;
|
|
100
|
-
} catch (err) {}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (skillDir === repoDir) {
|
|
104
|
-
for (const entry of entries) {
|
|
105
|
-
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') {
|
|
106
|
-
const subDir = path.join(repoDir, entry.name);
|
|
107
|
-
const subEntries = await fs.readdir(subDir, { withFileTypes: true });
|
|
108
|
-
for (const subEntry of subEntries) {
|
|
109
|
-
if (subEntry.isDirectory()) {
|
|
110
|
-
const possiblePath = path.join(subDir, subEntry.name, 'SKILL.md');
|
|
111
|
-
try {
|
|
112
|
-
await fs.access(possiblePath);
|
|
113
|
-
skillDir = path.join(subDir, subEntry.name);
|
|
114
|
-
skillMdPath = possiblePath;
|
|
115
|
-
break;
|
|
116
|
-
} catch (err) {}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} catch (dirErr) {
|
|
123
|
-
spinner.stop();
|
|
124
|
-
console.error(chalk.red(`Error: Repository '${skillName}' not found.`));
|
|
125
|
-
console.log(chalk.gray(`Try running \`${chalk.white('npx "@opendirectory.dev/skills" list')}\` to see available skills.`));
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
await fs.access(skillMdPath);
|
|
132
|
-
} catch (e) {
|
|
133
|
-
spinner.stop();
|
|
134
|
-
console.error(chalk.red(`Error: Skill '${skillName}' missing SKILL.md in registry.`));
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const actualSkillFolderName = path.basename(skillDir);
|
|
139
|
-
const finalSkillName = actualSkillFolderName === skillName ? skillName : actualSkillFolderName;
|
|
140
|
-
|
|
141
|
-
const target = options.target.toLowerCase();
|
|
142
|
-
|
|
143
|
-
const validTargets = ['opencode', 'claude', 'codex', 'gemini', 'anti-gravity', 'openclaw', 'hermes'];
|
|
144
|
-
|
|
145
|
-
if (validTargets.includes(target)) {
|
|
146
|
-
let targetFolder = '';
|
|
147
|
-
if (target === 'opencode') targetFolder = `~/.config/opencode/skills/${finalSkillName}`;
|
|
148
|
-
if (target === 'claude') targetFolder = `~/.claude/skills/${finalSkillName}`;
|
|
149
|
-
if (target === 'codex') targetFolder = `~/.codex/skills/${finalSkillName}`;
|
|
150
|
-
if (target === 'gemini') targetFolder = `~/.gemini/skills/${finalSkillName}`;
|
|
151
|
-
if (target === 'anti-gravity') targetFolder = `~/.gemini/antigravity/skills/${finalSkillName}`;
|
|
152
|
-
if (target === 'openclaw') targetFolder = `~/.openclaw/skills/${finalSkillName}`;
|
|
153
|
-
if (target === 'hermes') targetFolder = `~/.hermes/skills/${finalSkillName}`;
|
|
154
|
-
|
|
155
|
-
const { resolvePath } = require('./fs-adapters');
|
|
156
|
-
const resolvedDest = resolvePath(targetFolder);
|
|
157
|
-
await fs.mkdir(resolvedDest, { recursive: true });
|
|
158
|
-
await fs.cp(skillDir, resolvedDest, { recursive: true });
|
|
159
|
-
|
|
160
|
-
spinner.stop();
|
|
161
|
-
console.log(chalk.green(`Successfully installed ${chalk.bold(finalSkillName)}!`));
|
|
162
|
-
console.log(`\n ${chalk.cyan('Agent:')} ${target}`);
|
|
163
|
-
console.log(` ${chalk.cyan('Scope:')} Global`);
|
|
164
|
-
console.log(` ${chalk.cyan('Path:')} ${targetFolder}\n`);
|
|
165
|
-
} else {
|
|
166
|
-
spinner.stop();
|
|
167
|
-
console.error(chalk.red(`Error: Unsupported target '${target}'.`));
|
|
168
|
-
console.log(chalk.gray(`Supported targets: ${validTargets.join(', ')}`));
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
spinner.stop();
|
|
173
|
-
console.error(chalk.red('Failed to install skill.'));
|
|
174
|
-
console.error(error);
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
program.parse();
|
package/src/transformers.ts
DELETED