Skip to content

Latest commit

 

History

History
225 lines (170 loc) · 9.67 KB

File metadata and controls

225 lines (170 loc) · 9.67 KB

CLAUDE.md

Project guide for AI coding agents working on SkillNote.

Overview

SkillNote is a self-hosted skill registry for AI coding agents. It lets teams create, version, and distribute SKILL.md files across Claude Code, Cursor, Codex, OpenHands, and more. The app is offline-first: skills are stored in localStorage and sync to PostgreSQL when the backend is available.

Tech Stack

Layer Technology
Frontend Next.js 16, React 19, TypeScript, Tailwind CSS 4, Tiptap
Backend Python 3.12, FastAPI, SQLAlchemy 2, Alembic, Pydantic 2
Database PostgreSQL 16
CLI Node.js, TypeScript, Commander.js
Infra Docker, Docker Compose

Project Structure

skillnote/
├── src/                          # Next.js frontend (App Router)
│   ├── app/(app)/                # Route group: pages (home, skills, tags, collections, settings)
│   ├── components/
│   │   ├── skills/               # SkillDetail, SkillCard, editor, tabs (view/edit/history/comments)
│   │   ├── layout/               # Sidebar, TopBar, ConnectionBanner
│   │   ├── import/               # ImportModal (SKILL.md file import)
│   │   └── ui/                   # shadcn/ui primitives
│   └── lib/
│       ├── api/client.ts         # Fetch wrapper (apiRequest<T>) + SkillNoteApiError
│       ├── api/skills.ts         # All typed API calls (CRUD, comments, tags, versions)
│       ├── skills-store.ts       # Offline-first state (localStorage + API sync)
│       ├── skill-validation.ts   # Frontend validation (mirrors backend rules exactly)
│       ├── mock-data.ts          # TypeScript types + seed data
│       ├── markdown-utils.ts     # SKILL.md generation + parsing with YAML frontmatter
│       ├── hooks.ts              # useKeyboardShortcut, useClipboard, useLocalStorage
│       └── utils.ts              # cn() (clsx + tailwind-merge)
├── backend/                      # FastAPI backend
│   ├── app/api/                  # Route handlers (skills, comments, tags, publish, downloads)
│   ├── app/db/models/            # SQLAlchemy models (Skill, SkillVersion, SkillContentVersion, Comment)
│   ├── app/schemas/              # Pydantic request/response schemas
│   ├── app/validators/           # skill_validator.py (name/desc rules), bundle_validator.py (ZIP validation)
│   ├── app/core/config.py        # pydantic-settings with SKILLNOTE_ env prefix
│   ├── app/services/             # LocalBundleStorage (ZIP file storage)
│   ├── alembic/versions/         # 4 migrations (initial → rich fields → content versions → drop auth)
│   ├── tests/                    # pytest unit + integration tests
│   └── scripts/                  # seed_data.py, wait_for_db.py, smoke_test.sh
├── cli/                          # CLI tool (skillnote binary)
│   └── src/
│       ├── commands/             # login, list, add, check, update, remove, doctor
│       └── agents/               # Agent adapters (claude, cursor, codex, openclaw, openhands, universal)
├── e2e/                          # Playwright E2E tests
├── docker-compose.yml            # Full stack: postgres + api + web
└── Dockerfile                    # Next.js multi-stage build

Git Workflow

The master branch is protected. All changes must go through a feature branch and a pull request. Never push directly to master.

  1. Create a feature branch (git checkout -b feat/my-feature)
  2. Commit changes to the feature branch
  3. Push the branch and create a PR (gh pr create)
  4. Merge after approval

Development Setup

Full stack with Docker

docker compose up --build -d
# Web: http://localhost:3000  |  API: http://localhost:8082

Frontend hot-reload

docker compose up --build -d postgres api    # Backend in Docker
npm install && npm run dev                   # Frontend on localhost:3000

LAN access

SKILLNOTE_HOST=<your-server-ip> docker compose up --build -d

Key Architectural Patterns

State Management (No Library)

Skills state lives in localStorage under skillnote:skills. No Redux, Zustand, or Context.

  • getSkills() / writeStorage() read/write localStorage directly
  • Cross-component sync via window.dispatchEvent(new Event('skillnote:skills-changed'))
  • Home page re-syncs on: mount, 30s interval, window focus, skillnote:skills-changed event
  • Connection status is a module-level pub/sub (_connectionStatus + _listeners)

Offline-First Pattern

  1. Render immediately from localStorage (no loading flash)
  2. syncSkillsFromApi() merges API + local-only skills in the background
  3. Create/update: try API first, fall back to local with version increment
  4. Delete: API required (no offline delete)
  5. ConnectionBanner shows when offline with retry button

API Call Chain

Components → skills-store.ts → api/skills.ts → api/client.ts (apiRequest<T>)

Components never call API directly (except comments and tags).

API URL Resolution

Priority: localStorage['skillnote:api-url'] > NEXT_PUBLIC_API_BASE_URL env > http://localhost:8082

Validation (Mirrored Frontend/Backend)

Both sides enforce identical rules:

  • Name: max 64 chars, pattern ^[a-z0-9-]+$, no reserved words (anthropic, claude)
  • Description: max 1024 chars, non-empty
  • Frontend: src/lib/skill-validation.ts
  • Backend: backend/app/validators/skill_validator.py

Content Versioning

Every save creates a SkillContentVersion snapshot (auto-incremented integer). Users can browse history, compare, and restore any version. Published versions use semver and are distributed as checksummed ZIP bundles.

Authentication

Currently: No authentication. Auth tables were dropped in migration 0004. All API endpoints are open. The TODO: Re-enable when ACL is ready comments mark where auth will return.

Coding Conventions

Frontend

  • Use cn() from @/lib/utils for conditional class names
  • All pages are client components ('use client')
  • Icons: lucide-react only
  • UI primitives: shadcn/ui in src/components/ui/
  • Styling: Tailwind CSS 4, CSS variables for theming (defined in globals.css)
  • Fonts: DM Sans (body) + JetBrains Mono (code)
  • Toast notifications: sonner
  • Theme: next-themes with system default

Backend

  • All errors return {"error": {"code": "...", "message": "..."}} via the global exception handler
  • Config via pydantic-settings with SKILLNOTE_ prefix
  • DB sessions: get_db() dependency injection
  • Models use Mapped columns (SQLAlchemy 2.0 style)
  • Schemas use from_attributes=True for ORM compatibility

File Naming

  • React components: PascalCase (SkillEditTab.tsx, NewSkillModal.tsx)
  • Lib modules: kebab-case (skills-store.ts, skill-validation.ts)
  • Backend: snake_case (skill_validator.py, storage_service.py)

Testing

Layer Framework Location Run Command
Backend pytest backend/tests/ cd backend && pytest
Frontend Playwright e2e/ npx playwright test
CLI vitest cli/src/__tests__/ cd cli && npm test
  • E2E tests mock all /v1/** API calls via page.route() so they run without a backend
  • Backend integration tests hit the live API (skip if unreachable)

Docker

  • docker-compose.yml: 3 services (postgres, api, web)
  • Backend auto-runs: wait_for_db.pyalembic upgrade headseed_data.pyuvicorn
  • NEXT_PUBLIC_API_BASE_URL is a Docker build arg (baked at build time)
  • Frontend uses output: 'standalone' for minimal production image

Important Files

File Purpose
src/components/skills/skill-detail.tsx Largest component: tabs, command palette, keyboard shortcuts, swipe nav
src/lib/skills-store.ts Core state module: localStorage CRUD + API sync + offline fallback
src/lib/api/client.ts Base apiRequest<T>() fetch wrapper
src/lib/skill-validation.ts Frontend validation rules
backend/app/api/skills.py Main CRUD + versioning endpoints
backend/app/validators/skill_validator.py Name/description validation
backend/app/validators/bundle_validator.py ZIP upload validation (path traversal, size limits, frontmatter)
backend/app/core/config.py All env vars with defaults

Versioning

App version is the single source of truth in package.json ("version": "x.y.z").

  • next.config.ts reads package.json and exposes NEXT_PUBLIC_APP_VERSION at build time
  • Sidebar footer displays the version next to the connection status indicator
  • Settings page shows the version in the About section
  • CHANGELOG.md in the project root documents all releases (git-only, not shown in UI)

When bumping the version:

  1. Update "version" in package.json
  2. Add a new entry to CHANGELOG.md

SKILL.md Format

---
name: my-skill
description: What it does and when to trigger it.
---

# My Skill

Instructions for the AI agent...

Keyboard Shortcuts

Key Action Context
N New skill Home
Cmd/Ctrl+K Command palette / search Anywhere
Cmd/Ctrl+S Save Editor
E Edit Skill detail
Escape Close / go back Anywhere