Skip to content

Commit 3d04ce8

Browse files
author
Kevin Hopper
committed
Add extension-dev skill for standardized extension development
New skill codifies the full workflow: scaffold, develop, test, publish. Captures lessons from recurring friction (broken panel imports, missed registry syncs, forgotten gateway restarts). Also fixes the dashboard panel template to use the correct dynamic import pattern with appRoot.
1 parent 333f276 commit 3d04ce8

5 files changed

Lines changed: 311 additions & 14 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ Consult `skills/superpowers.md` first — it routes user intent to the right ski
376376
- `blog.md` — Blog creation, publishing, theming, export
377377
- `songbook.md` — Personal chord book: ChordPro charts, transposition, chord diagrams, setlists, music theory
378378
- `data-backends.md` — External data backend registration and knowledge capture workflow
379+
- `extension-dev.md` — Extension development: scaffold, test, and publish bundles, panels, MCP servers, and skills
379380
- `network-setup.md` — Tailscale remote access guidance
380381
- `add-ons.md` — Add-on browsing, installation, removal
381382
- `scheduling.md` — Scheduled and recurring task management

docs/skills/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ Skills are markdown files in `skills/` that define behavioral prompts for the AI
99
| **I18n** | `i18n.md` | Defines how Crow adapts to each user's preferred language. All user-facing output is delivered in the user's language. S |
1010
| **Memory Management** | `memory-management.md` | Store, search, and retrieve persistent memories across sessions. Use this skill to maintain context about the user, thei |
1111
| **Plan Review** | `plan-review.md` | Before executing multi-step or significant tasks, Crow outlines its approach as an inline plan and waits for user approv |
12-
| **Reflection** | `reflection.md` | A deep-dive friction analysis skill. Identifies what went wrong, analyzes root causes, and proposes concrete improvement |
12+
| **Reflection** | `reflection.md` | A meta-skill for evaluating *how well things went* and proposing concrete fixes. Identifies friction, maps root causes, |
1313
| **Research Pipeline** | `research-pipeline.md` | Manage academic and professional research with full source tracking, APA citations, and verification. Every piece of inf |
14-
| **Session Context** | `session-context.md` | Automatically load and save context at the beginning and end of sessions. Ensures continuity across conversations. |
15-
| **Session Summary** | `session-summary.md` | Quick session summary — records deliverables, decisions, and next steps |
14+
| **Session Summary** | `session-summary.md` | Save session summary and key learnings to Crow memory |
1615
| **Skill Writing** | `skill-writing.md` | This skill enables the AI to create, modify, and propose new skill files (`skills/*.md`) when existing skills don't cove |
1716
| **Superpowers** | `superpowers.md` | This is the master routing skill. Consult this **before every task** to determine which skills and tools to activate. It |
1817

@@ -45,6 +44,7 @@ Skills are markdown files in `skills/` that define behavioral prompts for the AI
4544
| Skill | File | Purpose |
4645
|---|---|---|
4746
| **Discord** | `discord.md` | Interact with Discord servers — channels, messages, threads — through the Discord MCP server. Monitor community discussi |
47+
| **Extension Dev** | `extension-dev.md` | Develop, test, and publish Crow extensions (bundles, panels, MCP servers, skills) |
4848
| **Filesystem** | `filesystem.md` | Access and manage local files and directories through the Filesystem MCP server. Read documents, organize research mater |
4949
| **Github** | `github.md` | Interact with GitHub — repositories, issues, pull requests, code — through the GitHub MCP server. Track development work |
5050
| **Google Chat** | `google-chat.md` | Interact with Google Chat — spaces, messages, threads — through the Google Workspace MCP server. Google Chat is already |

skills/extension-dev.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
---
2+
name: extension-dev
3+
description: Develop, test, and publish Crow extensions (bundles, panels, MCP servers, skills)
4+
triggers:
5+
- build extension
6+
- create bundle
7+
- develop panel
8+
- new add-on for crow
9+
- publish extension
10+
tools:
11+
- crow-memory
12+
- filesystem
13+
---
14+
15+
# Extension Development Workflow
16+
17+
## When to Activate
18+
19+
- Building a new Crow extension, bundle, panel, or MCP server
20+
- Modifying an existing extension
21+
- Publishing an extension to the registry
22+
23+
**This skill covers extension-specific development.** For the general quality checklist (docs, CLAUDE.md, superpowers.md updates), follow `crow-developer.md` after development is complete.
24+
25+
**Not this skill:** If the user wants to *install, browse, or remove* an existing extension, use `add-ons.md` instead.
26+
27+
## Phase 1: Scaffold
28+
29+
### Choose a type
30+
31+
| Type | Use when | Directory |
32+
|------|----------|-----------|
33+
| `bundle` | Docker services + optional panel/server/skill | `bundles/<id>/` |
34+
| `mcp-server` | Standalone MCP server + optional panel/skill | `bundles/<id>/` |
35+
| `skill` | Behavioral prompt only, no code | `skills/<id>.md` |
36+
| `panel` | Dashboard panel only, no backend | `bundles/<id>/` |
37+
38+
### Create directory structure
39+
40+
```
41+
bundles/<id>/
42+
manifest.json # Required — metadata
43+
panel/<id>.js # Dashboard panel (if applicable)
44+
panel/<id>-routes.js # Express routes for panel API (if needed)
45+
server/index.js # MCP server entry (if applicable)
46+
skills/<id>.md # Skill file (if applicable)
47+
docker-compose.yml # Docker services (bundle type only)
48+
```
49+
50+
### Write manifest.json
51+
52+
Use the template for your type. **Critical:** `manifest.panel` MUST be a string path (e.g., `"panel/<id>.js"`), never an object.
53+
54+
#### Bundle with panel + server
55+
56+
```json
57+
{
58+
"id": "my-extension",
59+
"name": "My Extension",
60+
"version": "1.0.0",
61+
"description": "Short description for the Extensions panel",
62+
"type": "bundle",
63+
"author": "Crow",
64+
"category": "productivity",
65+
"tags": ["relevant", "search", "terms"],
66+
"icon": "book",
67+
"panel": "panel/my-extension.js",
68+
"skills": ["skills/my-extension.md"],
69+
"server": {
70+
"command": "node",
71+
"args": ["server/index.js"],
72+
"cwd": "."
73+
},
74+
"requires": {
75+
"env": [],
76+
"min_ram_mb": 128,
77+
"min_disk_mb": 100
78+
},
79+
"env_vars": []
80+
}
81+
```
82+
83+
#### MCP server only
84+
85+
```json
86+
{
87+
"id": "my-server",
88+
"name": "My Server",
89+
"version": "1.0.0",
90+
"description": "Short description",
91+
"type": "mcp-server",
92+
"author": "Crow",
93+
"category": "productivity",
94+
"tags": ["relevant", "terms"],
95+
"icon": "brain",
96+
"server": {
97+
"command": "node",
98+
"args": ["server/index.js"],
99+
"envKeys": ["MY_API_KEY"]
100+
},
101+
"requires": {
102+
"env": ["MY_API_KEY"],
103+
"min_ram_mb": 128,
104+
"min_disk_mb": 50
105+
},
106+
"env_vars": [
107+
{ "name": "MY_API_KEY", "description": "API key for the service", "required": true, "secret": true }
108+
]
109+
}
110+
```
111+
112+
#### Skill only
113+
114+
```json
115+
{
116+
"id": "my-skill",
117+
"name": "My Skill",
118+
"version": "1.0.0",
119+
"description": "Short description",
120+
"type": "skill",
121+
"author": "Crow",
122+
"category": "productivity",
123+
"tags": ["relevant", "terms"],
124+
"icon": "book",
125+
"skills": ["skills/my-skill.md"],
126+
"requires": { "env": [], "min_ram_mb": 0, "min_disk_mb": 1 },
127+
"env_vars": []
128+
}
129+
```
130+
131+
## Phase 2: Develop
132+
133+
### Panel Rules (CRITICAL)
134+
135+
Panels are copied to `~/.crow/panels/` when installed. Relative imports break because the file is no longer in the repo tree. Follow these rules without exception:
136+
137+
**1. NEVER use static ESM imports for gateway shared modules**
138+
139+
```js
140+
// WRONG — breaks when installed
141+
import { escapeHtml } from "../../../../servers/gateway/dashboard/shared/components.js";
142+
```
143+
144+
**2. ALWAYS use dynamic imports with appRoot**
145+
146+
```js
147+
// CORRECT — works everywhere
148+
export default {
149+
id: "my-panel",
150+
name: "My Panel",
151+
icon: "default",
152+
route: "/dashboard/my-panel",
153+
navOrder: 50,
154+
155+
async handler(req, res, { db, layout, lang, appRoot }) {
156+
const { pathToFileURL } = await import("node:url");
157+
const { join } = await import("node:path");
158+
const { existsSync } = await import("node:fs");
159+
160+
// Import shared components via appRoot
161+
const componentsPath = join(appRoot, "servers/gateway/dashboard/shared/components.js");
162+
const { escapeHtml, section, badge, dataTable, formatDate } = await import(pathToFileURL(componentsPath).href);
163+
164+
// Resolve bundle server directory (installed vs repo)
165+
const installedServerDir = join(process.env.HOME || "", ".crow", "bundles", "my-panel", "server");
166+
const repoServerDir = join(appRoot, "bundles", "my-panel", "server");
167+
const bundleServerDir = existsSync(installedServerDir) ? installedServerDir : repoServerDir;
168+
169+
async function importBundleModule(name) {
170+
return import(pathToFileURL(join(bundleServerDir, name)).href);
171+
}
172+
173+
// If helper functions need shared components, pass them as context
174+
const ctx = { escapeHtml, section, badge, dataTable, formatDate, importBundleModule };
175+
const tabContent = await renderTab(db, ctx);
176+
177+
return layout({ title: "My Panel", content: tabContent });
178+
},
179+
};
180+
181+
// Helper functions receive shared components via context object
182+
async function renderTab(db, { escapeHtml, section, dataTable }) {
183+
// Use components here
184+
return section("Data", dataTable(["Col"], []));
185+
}
186+
```
187+
188+
**3. Handler signature must include `appRoot`**
189+
190+
```js
191+
async handler(req, res, { db, layout, lang, appRoot })
192+
```
193+
194+
The `appRoot` parameter resolves to the Crow repo root directory. Use it for all cross-module imports.
195+
196+
### MCP Server Rules
197+
198+
- Use the server factory pattern: export `createXxxServer(dbPath?, options?)` returning `McpServer`
199+
- The server must resolve `@modelcontextprotocol/sdk`. Options:
200+
- Include a `package.json` with the dependency (installer runs `npm install`)
201+
- Or import from the gateway's `node_modules` using `appRoot` resolution
202+
- All Zod string schemas should include `.max()` constraints
203+
204+
### Skill File Rules
205+
206+
- YAML frontmatter: `name`, `description`, `triggers` (list), `tools` (list)
207+
- Include both EN and ES trigger phrases in the trigger table
208+
- Reference MCP tool names the skill uses
209+
210+
## Phase 3: Test Locally
211+
212+
### Server
213+
214+
```bash
215+
node bundles/<id>/server/index.js
216+
# Should start without errors (Ctrl-C to stop)
217+
```
218+
219+
### Panel
220+
221+
```bash
222+
# Copy panel to installed location
223+
cp bundles/<id>/panel/<id>.js ~/.crow/panels/
224+
225+
# Add to panels.json if not already listed
226+
# (check with: cat ~/.crow/panels.json)
227+
228+
# Restart the gateway
229+
# Then verify at /dashboard/<id> in the Crow's Nest
230+
```
231+
232+
### Full lifecycle test
233+
234+
1. Install from the Extensions panel in the Crow's Nest
235+
2. Verify panel appears in sidebar and loads correctly
236+
3. Test core functionality (forms, queries, interactions)
237+
4. Uninstall from Extensions panel
238+
5. Reinstall — verify clean install works
239+
240+
## Phase 4: Publish
241+
242+
### Register (follow crow-developer checklist)
243+
244+
1. Add entry to `registry/add-ons.json`
245+
2. Update CLAUDE.md — Skills Reference, any new DB tables or tools
246+
3. Add trigger row to `skills/superpowers.md` (EN + ES)
247+
4. Run `npm run sync-skills` to regenerate `docs/skills/index.md`
248+
5. Update relevant docs pages
249+
250+
### Push to crow-addons repo
251+
252+
The Extensions panel fetches from the remote GitHub registry. Without this step, the extension is invisible to users.
253+
254+
```bash
255+
cd ~/crow-addons
256+
git pull origin main
257+
258+
# Add/update the entry in registry.json
259+
# Copy crow-addon.json to <id>/ subdirectory if needed
260+
261+
git add -A
262+
git commit -m "Add <id> extension"
263+
git push origin main
264+
```
265+
266+
Wait ~5 minutes for GitHub CDN cache to clear.
267+
268+
### Deploy
269+
270+
1. Commit and push changes to the crow repo
271+
2. Restart the gateway on all instances where Crow runs
272+
3. Pull the latest code on remote instances first
273+
274+
### Verify end-to-end
275+
276+
1. Open the Extensions panel — the new extension should appear
277+
2. Install it
278+
3. Confirm panel appears in sidebar (if applicable)
279+
4. Test core functionality
280+
5. Uninstall and reinstall to verify clean lifecycle
281+
282+
## Common Mistakes
283+
284+
| Mistake | Consequence | Prevention |
285+
|---------|-------------|------------|
286+
| Static ESM imports in panel | Panel crashes when installed to `~/.crow/panels/` | Use dynamic imports with `appRoot` |
287+
| `manifest.panel` as object | Install crashes: "path must be string" | Always use string: `"panel/<id>.js"` |
288+
| Forgot crow-addons repo sync | Extension invisible in Extensions panel | Always push to crow-addons after crow repo |
289+
| Forgot gateway restart | Old code still running, changes don't take effect | Restart after any server/panel/route change |
290+
| Panel missing `appRoot` in handler | Can't resolve shared components | Use full signature: `{ db, layout, lang, appRoot }` |
291+
| Helper functions use module-level imports | Imports resolved at wrong path | Pass shared components via context object |
292+
| MCP server missing dependencies | Server fails to start: "Cannot find package" | Include package.json or symlink node_modules |
293+
| No EN + ES triggers in superpowers.md | Skill won't activate for Spanish-speaking users | Always add both language triggers |

skills/superpowers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ This is the master routing skill. Consult this **before every task** to determin
5555
| "news", "articles", "feed", "RSS", "briefing", "podcast", "youtube" | "noticias", "artículos", "podcast", "youtube" | media (add-on) | crow-media |
5656
| "crow's nest", "dashboard", "settings", "control panel" | "panel", "configuración", "panel de control" | network-setup | (dashboard routes) |
5757
| "add-on", "extension", "install plugin", "browse extensions" | "complemento", "extensión", "instalar plugin" | add-ons | crow-memory, filesystem |
58+
| "build extension", "create bundle", "develop panel", "new add-on for crow", "publish extension" | "crear extensión", "desarrollar panel", "nuevo complemento para crow" | extension-dev | crow-memory, filesystem |
5859
| "what can you do", "getting started", "new to crow" | "qué puedes hacer", "cómo empezar", "nuevo en crow" | onboarding-tour | crow-memory |
5960
| "tailscale", "remote access", "network setup", "private access", "VPN" | "tailscale", "acceso remoto", "configurar red", "acceso privado", "red privada" | network-setup, tailscale | (documentation) |
6061
| "report bug", "file issue", "feature request", "found a bug" | "reportar bug", "crear issue", "solicitar función" | bug-report | crow-memory, github |

templates/dashboard-panel.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,34 @@
44
* Place this file in ~/.crow/panels/ and add the panel ID to ~/.crow/panels.json
55
* to enable it. See docs/developers/creating-panels.md for the full guide.
66
*
7+
* IMPORTANT: Use dynamic imports with appRoot (not static ESM imports).
8+
* Panels are copied to ~/.crow/panels/ when installed — relative imports
9+
* from the repo tree will break. See skills/extension-dev.md for details.
10+
*
711
* Panel manifest fields:
812
* id — Unique panel identifier (used in URL: /dashboard/:id)
913
* name — Display name in Crow's Nest sidebar navigation
1014
* icon — Icon key: messages, edit, files, settings, extensions, or default
1115
* route — Full route path (must match /dashboard/:id)
1216
* navOrder — Sort order in sidebar (lower = higher)
13-
* handler — Async function(req, res, { db, layout }) => HTML string
17+
* handler — Async function(req, res, { db, layout, lang, appRoot }) => HTML string
1418
*/
1519

16-
import {
17-
escapeHtml,
18-
dataTable,
19-
section,
20-
formField,
21-
badge,
22-
formatDate,
23-
} from "../servers/gateway/dashboard/shared/components.js";
24-
2520
export default {
2621
id: "my-panel",
2722
name: "My Panel",
2823
icon: "default",
2924
route: "/dashboard/my-panel",
3025
navOrder: 50,
3126

32-
async handler(req, res, { db, layout }) {
27+
async handler(req, res, { db, layout, lang, appRoot }) {
28+
// Dynamic imports — resolve shared components via appRoot
29+
const { pathToFileURL } = await import("node:url");
30+
const { join } = await import("node:path");
31+
32+
const componentsPath = join(appRoot, "servers/gateway/dashboard/shared/components.js");
33+
const { escapeHtml, dataTable, section, formField, badge, formatDate } = await import(pathToFileURL(componentsPath).href);
34+
3335
// Handle form submissions
3436
if (req.method === "POST") {
3537
const { action } = req.body;

0 commit comments

Comments
 (0)