@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 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.72",
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
- });
@@ -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
- });
@@ -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();
@@ -1,6 +0,0 @@
1
- export interface Skill {
2
- name: string;
3
- description: string;
4
- content: string;
5
- globs?: string;
6
- }
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "dist"
6
- },
7
- "include": ["src/**/*"]
8
- }