A lightweight content management system built with Go, featuring a modern admin interface, session-based authentication, SQLite storage, and extensible architecture with themes and modules.
- Page Management: Create, edit, publish, and version pages with a rich content editor
- Video Embedding: Embed YouTube, Vimeo, and Dailymotion videos in pages with responsive rendering
- Scheduled Publishing: Schedule pages to publish at a future date/time
- Media Library: Upload and manage images, documents, and videos with automatic image processing
- Automatic thumbnail and variant generation
- Folder organization
- Featured image support for pages
- Menu Builder: Create navigation menus with drag-and-drop ordering
- Hierarchical menu structures
- Link to pages or external URLs
- Multiple menu locations
- Full-Text Search: Built-in SQLite FTS5 search for fast content discovery
- Categories: Organize content with hierarchical categories
- Tags: Add flat taxonomy tags to pages
- Form Builder: Create contact forms, surveys, and data collection forms
- Multiple field types (text, email, textarea, select, checkbox, radio)
- Form submissions management
- Read/unread status tracking
- Email notifications
- Multiple Themes: Switch between different frontend themes
- Theme Settings: Configurable theme options (colors, layout, etc.)
- Template Override: Themes can customize page templates
- Static Assets: Theme-specific CSS, JavaScript, and images
- Extensible Architecture: Add custom functionality via modules
- Module Lifecycle: Init, routes, admin routes, and shutdown hooks
- Module Migrations: Modules can have their own database migrations
- Template Functions: Modules can add custom template functions
- Active Status Toggle: Enable/disable modules from admin UI without restart
- Module Translations: Modules can embed their own i18n locale files
- Full CRUD API: Complete REST API for pages, media, tags, and categories
- API Key Authentication: Secure API access with bearer token authentication
- Permission-Based Access: Fine-grained permissions (read/write per resource)
- Rate Limiting: Per-key and global rate limiting
- API Documentation: Built-in API documentation page
- Meta Tags: Custom title, description, and keywords per page
- Open Graph: Full Open Graph and Twitter Card support
- Sitemap: Auto-generated sitemap.xml
- Robots.txt: Configurable robots.txt generation
- Canonical URLs: Set canonical URLs to avoid duplicate content
- NoIndex/NoFollow: Control search engine indexing per page
- User Management: Role-based access control (admin/editor)
- Authentication: Secure session-based authentication with argon2id password hashing
- Event Logging: Comprehensive audit trail for all actions
- Admin Dashboard: Modern responsive UI with HTMX and Alpine.js
- Statistics overview
- Recent submissions widget
- Quick actions
- Collapsible sidebar with persistent state
- Cache Management: View cache stats and clear cache
- API Key Management: Create and manage API keys
- Bulk List Actions: Multi-select and bulk delete/revoke on paged admin lists (pages, tags, users, API keys, media, and form submissions)
- Per-Page Selector: Choose items per page on delete-capable admin lists (URL query
per_page, current-page state in URL only) - List Sorting: Sort delete-capable admin lists by safe whitelisted columns with clear active sort highlighting (URL queries
sort+dir) - SQLite Database: Zero-configuration embedded database with migrations
- Content Translation: Translate pages, categories, and tags into multiple languages
- Language Management: Configure site languages with ISO 639-1 codes
- Translation Linking: Link content across languages for seamless switching
- Language Switcher: Built-in frontend component for language navigation
- URL Prefixes: Language-prefixed URLs (e.g.,
/ru/about-us) - RTL Support: Right-to-left language support
- Admin UI Localization: Translatable admin interface (English + Russian included)
- Event System: Trigger webhooks on content events (create, update, delete, publish)
- Delivery Tracking: Monitor delivery status and response codes
- Retry Logic: Exponential backoff retry for failed deliveries
- HMAC Signatures: Secure payloads with HMAC-SHA256 signatures
- Custom Headers: Add custom headers to webhook requests
- Dead Letter Queue: Track permanently failed deliveries
- Event Debouncing: Coalesce rapid-fire events to reduce webhook noise
- JSON Export: Export site content to portable JSON format
- ZIP Export: Include media files in export archives
- Selective Export: Choose which content types to include
- JSON Import: Import content from JSON files
- ZIP Import: Restore media files from archives
- Conflict Resolution: Skip, overwrite, or rename on conflicts
- Dry Run Mode: Preview import changes before applying
- Multi-Level Caching: In-memory and optional Redis caching
- Redis Support: Distributed caching for multi-instance deployments
- Response Compression: Gzip compression for HTML and JSON responses
- Graceful Shutdown: Clean shutdown with request draining
- Health Check:
/healthendpoint for monitoring
- Go 1.26 or later
- Node.js (npm) for frontend dependencies
- sqlc for SQL code generation
- templ for type-safe HTML templates
- goose for database migrations
- Dart Sass for SCSS compilation
- libvips for image processing (required for media library)
macOS:
brew install vipsUbuntu/Debian:
sudo apt-get install libvips-devFedora:
sudo dnf install vips-devel-
Clone the repository:
git clone https://github.com/olegiv/ocms-go.git cd ocms-go -
Install dependencies:
go mod download
-
Install required tools:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest go install github.com/a-h/templ/cmd/templ@latest go install github.com/pressly/goose/v3/cmd/goose@latest
-
Generate code:
sqlc generate templ generate
-
Build assets (installs npm dependencies and compiles SCSS):
make assets
| Variable | Description | Default | Required |
|---|---|---|---|
OCMS_SESSION_SECRET |
Secret key for session encryption (min 32 bytes) | - | Yes |
OCMS_DB_PATH |
Path to SQLite database file | ./data/ocms.db |
No |
OCMS_SERVER_HOST |
Server host address | localhost |
No |
OCMS_SERVER_PORT |
Server port number | 8080 |
No |
OCMS_ENV |
Environment mode (development/production) |
development |
No |
OCMS_LOG_LEVEL |
Log level (debug/info/warn/error) |
info |
No |
OCMS_CUSTOM_DIR |
Directory for custom themes and modules | ./custom |
No |
OCMS_ACTIVE_THEME |
Active theme (overrides DB/admin setting) | default |
No |
OCMS_DO_SEED |
Seed database with default admin and config | false |
No |
OCMS_CACHE_TTL |
Default cache TTL in seconds | 3600 |
No |
OCMS_REDIS_URL |
Redis URL for distributed caching | - | No |
OCMS_CACHE_PREFIX |
Redis key prefix | ocms: |
No |
OCMS_CACHE_MAX_SIZE |
Max entries for in-memory cache | 10000 |
No |
OCMS_HCAPTCHA_SITE_KEY |
hCaptcha site key for login protection | - | No |
OCMS_HCAPTCHA_SECRET_KEY |
hCaptcha secret key for login protection | - | No |
OCMS_HCAPTCHA_DISABLED |
Force-disable hCaptcha regardless of database settings | false |
No |
OCMS_GEOIP_DB_PATH |
Path to GeoLite2-Country.mmdb for country detection | - | No |
OCMS_UPLOADS_DIR |
Directory for uploaded media files | ./uploads |
No |
OCMS_TRUSTED_PROXIES |
Trusted reverse-proxy CIDRs/IPs; forwarding headers are ignored unless peer is trusted | - | No |
OCMS_REQUIRE_TRUSTED_PROXIES |
Fail startup in production if trusted proxy CIDRs/IPs are not configured | false (true in production when unset) |
No |
OCMS_API_ALLOWED_CIDRS |
Global source CIDRs/IPs allowed to use API keys | - | No |
OCMS_REQUIRE_API_ALLOWED_CIDRS |
Fail API key auth when global API source CIDRs are not configured | false (true in production when unset) |
No |
OCMS_REQUIRE_API_KEY_EXPIRY |
Require API keys to have expiration timestamps | false (true in production when unset) |
No |
OCMS_REQUIRE_API_KEY_SOURCE_CIDRS |
Require API keys to have per-key source CIDR restrictions | false (true in production when unset) |
No |
OCMS_REVOKE_API_KEY_ON_SOURCE_IP_CHANGE |
Deactivate API keys when source IP changes and the key has no per-key CIDRs | false (true in production when unset) |
No |
OCMS_API_KEY_MAX_TTL_DAYS |
Maximum API key lifetime in days (0 disables, max 365) |
0 (90 in production when unset) |
No |
OCMS_EMBED_ALLOWED_ORIGINS |
Allowed browser origins for public embed proxy routes; required for working browser embed requests in production | - | No |
OCMS_EMBED_ALLOWED_UPSTREAM_HOSTS |
Allowed upstream hosts for embed provider API endpoints | - | No |
OCMS_REQUIRE_EMBED_ALLOWED_ORIGINS |
Fail startup in production if embed proxy is active without origin allowlist | false (true in production when unset) |
No |
OCMS_REQUIRE_EMBED_ALLOWED_UPSTREAM_HOSTS |
Fail startup in production if embed proxy is active without upstream host allowlist | false (true in production when unset) |
No |
OCMS_EMBED_PROXY_TOKEN |
Secret used to mint short-lived signed embed proxy tokens; required for active embed proxy in production | - | No |
OCMS_REQUIRE_EMBED_PROXY_TOKEN |
Enforce embed proxy token requirement in non-production too | false |
No |
OCMS_REQUIRE_HTTPS_OUTBOUND |
Require HTTPS for outbound integration URLs | false (true in production when unset) |
No |
OCMS_REQUIRE_FORM_CAPTCHA |
Require captcha on all public form submissions | false (true in production when unset) |
No |
OCMS_WEBHOOK_FORM_DATA_MODE |
form.submitted payload data mode (redacted/none/full) |
redacted |
No |
OCMS_REQUIRE_WEBHOOK_FORM_DATA_MINIMIZATION |
Fail startup in production when form webhook payload mode is full |
false (true in production when unset) |
No |
OCMS_WEBHOOK_ALLOWED_HOSTS |
Allowed destination hosts for active webhook deliveries (exact hostname match) | - | No |
OCMS_REQUIRE_WEBHOOK_ALLOWED_HOSTS |
Fail startup in production when active webhooks exist without destination host allowlist | false (true in production when unset) |
No |
OCMS_SANITIZE_PAGE_HTML |
Sanitize page HTML before rendering to visitors | false (true in production when unset) |
No |
OCMS_REQUIRE_SANITIZE_PAGE_HTML |
Fail startup in production if page HTML sanitization is disabled | false (true in production when unset) |
No |
OCMS_BLOCK_SUSPICIOUS_PAGE_HTML |
Reject page writes containing suspicious HTML patterns | false (true in production when unset) |
No |
OCMS_REQUIRE_BLOCK_SUSPICIOUS_PAGE_HTML |
Fail startup in production when suspicious page markup blocking is disabled or existing pages contain suspicious markers | false (true in production when unset) |
No |
OCMS_DEMO_MODE |
Enable demo content seeding (users, pages, media) | false |
No |
# Set required environment variable
export OCMS_SESSION_SECRET="your-secret-key-at-least-32-bytes"
# Install repository-managed git hooks (run once per clone)
make install-hooks
# Run with asset compilation
make dev
# Or run without rebuilding assets
make run| Command | Description |
|---|---|
make dev |
Build assets and run development server |
make run |
Run development server (no asset build) |
make stop |
Stop development server on port 8080 |
make restart |
Stop and restart development server |
make build |
Build binary to bin/ocms |
make build-prod |
Build optimized binary (stripped, trimmed) |
make build-linux-amd64 |
Cross-compile for Linux AMD64 |
make build-darwin-arm64 |
Cross-compile for macOS ARM64 |
make build-all-platforms |
Build for Linux AMD64 and macOS ARM64 |
make test |
Run all tests |
make clean |
Remove build artifacts |
make clean-db |
Remove database files |
make migrate-up |
Apply pending migrations |
make migrate-down |
Rollback last migration |
make migrate-status |
Show migration status |
make migrate-create |
Create new migration file |
make assets |
Install npm deps and compile SCSS |
make sqlc |
Regenerate sqlc code from SQL queries |
make templ |
Regenerate templ Go code from .templ files |
make deploy-binary |
Deploy binary to remote server (no custom content) |
make commit-prepare |
Proxy to Claude slash command /commit-prepare |
make commit-do |
Proxy to Claude slash command /commit-do |
make code-quality |
Proxy to Claude slash command /code-quality |
make security-audit |
Proxy to Claude slash command /security-audit |
make commit-prepare-local |
Run local commit-prepare shell script |
make commit-do-local |
Run local commit-do shell script |
make code-quality-local |
Run local code quality shell script (golangci-lint, nilaway, go test) |
make security-audit-local |
Run local security audit shell script (writes to .audit/) |
make install-hooks |
Configure git to use repository-managed hooks from .githooks |
make check-no-absolute-paths |
Fail if tracked files contain local absolute paths (/Users/..., /home/..., C:\Users\...) |
On first run with OCMS_DO_SEED=true, the application seeds a default admin user:
- Email: admin@example.com
- Password: changeme1234
Change these credentials immediately after first login.
With OCMS_DEMO_MODE=true (requires OCMS_DO_SEED=true), additional demo content is seeded including sample pages, categories, tags, media, and menu items. Two demo users are created:
- Admin: demo@example.com / demo1234demo
- Editor: editor@example.com / demo1234demo
See docs/demo-deployment.md for Fly.io deployment and demo configuration details.
# Clone the repository
git clone https://github.com/olegiv/ocms-go.git
cd ocms-go
# Start with Docker Compose (generates session secret automatically)
OCMS_SESSION_SECRET=$(openssl rand -base64 32) OCMS_DO_SEED=true docker compose up -d
# View logs
docker compose logs -f ocmsAccess the admin panel at http://localhost:8080/admin with:
- Email: admin@example.com
- Password: changeme1234
| Command | Description |
|---|---|
docker compose up -d |
Start oCMS |
docker compose --profile redis up -d |
Start with Redis caching |
docker compose down |
Stop services |
docker compose logs -f ocms |
View logs |
docker compose pull && docker compose up -d |
Update to latest |
| Volume | Container Path | Purpose |
|---|---|---|
ocms_data |
/app/data |
SQLite database |
ocms_uploads |
/app/uploads |
Media uploads |
./custom |
/app/custom |
Custom themes and modules |
# Build with version info
docker build \
--build-arg VERSION=$(git describe --tags --always) \
--build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
--build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t ocms:latest .Create a .env file for production:
OCMS_SESSION_SECRET=your-secure-secret-key-at-least-32-bytes
OCMS_ENV=production
OCMS_DO_SEED=false
OCMS_ACTIVE_THEME=default
OCMS_TRUSTED_PROXIES=127.0.0.1/32,10.0.0.0/8
OCMS_API_ALLOWED_CIDRS=203.0.113.0/24
# Hardened embed proxy baseline (when embed module/provider is enabled)
# Origin matching is exact (scheme + host). Include all real hostnames.
OCMS_EMBED_ALLOWED_ORIGINS=https://example.com,https://www.example.com
OCMS_EMBED_ALLOWED_UPSTREAM_HOSTS=api.dify.ai
OCMS_REQUIRE_EMBED_ALLOWED_ORIGINS=true
OCMS_REQUIRE_EMBED_ALLOWED_UPSTREAM_HOSTS=true
OCMS_EMBED_PROXY_TOKEN=replace-with-embed-proxy-token
OCMS_WEBHOOK_ALLOWED_HOSTS=hooks.example.com,events.example.com
# Required when OCMS_REQUIRE_FORM_CAPTCHA is enabled (enabled by default in production)
OCMS_HCAPTCHA_SITE_KEY=your-site-key
OCMS_HCAPTCHA_SECRET_KEY=your-secret-key
# Optional: Redis caching
OCMS_REDIS_URL=redis://redis:6379/0Then start with:
docker compose --profile redis up -docms-go/
├── cmd/ocms/ # Application entry point
├── docs/ # Documentation
│ ├── multi-language.md # Multi-language guide
│ ├── webhooks.md # Webhooks configuration
│ ├── import-export.md # Import/export guide
│ └── reverse-proxy.md # Nginx/Apache/NPM setup
├── internal/
│ ├── auth/ # Password hashing utilities
│ ├── cache/ # Caching layer (memory + Redis)
│ ├── config/ # Configuration loading
│ ├── handler/ # HTTP handlers
│ │ └── api/ # REST API handlers
│ ├── i18n/ # Internationalization (admin UI)
│ ├── imaging/ # Image processing (thumbnails, variants)
│ ├── middleware/ # HTTP middleware (auth, API, rate limiting)
│ ├── model/ # Domain models
│ ├── module/ # Module system (registry, hooks)
│ ├── render/ # Template rendering
│ ├── scheduler/ # Cron-based task scheduler
│ ├── seo/ # SEO utilities (sitemap, robots.txt, meta)
│ ├── service/ # Business logic (media, menus, forms)
│ ├── session/ # Session management
│ ├── store/ # Database layer (sqlc generated)
│ │ ├── migrations/ # Goose SQL migrations
│ │ └── queries/ # sqlc query definitions
│ ├── theme/ # Theme loading and management
│ ├── themes/ # Embedded core themes (default, developer)
│ ├── transfer/ # Import/export functionality
│ ├── util/ # Utility functions (slug generation)
│ └── webhook/ # Webhook system
├── modules/ # Custom modules directory
│ └── example/ # Example module implementation
├── custom/ # User content directory (gitignored)
│ ├── themes/ # Custom themes (override or extend core)
│ └── modules/ # Custom modules (future use)
├── web/
│ ├── static/ # Static assets (CSS, JS)
│ │ └── scss/ # SCSS source files
│ └── templates/ # HTML templates
│ ├── admin/ # Admin panel templates
│ ├── api/ # API documentation templates
│ ├── auth/ # Login/logout templates
│ ├── errors/ # Error pages (404, 403, 500)
│ ├── layouts/ # Base layouts
│ └── partials/ # Reusable components
├── uploads/ # Media uploads directory
├── scripts/ # Build scripts
├── Makefile # Development commands
├── package.json # npm dependencies (htmx, alpine.js)
└── sqlc.yaml # sqlc configuration
The CMS provides a RESTful API for programmatic access to content.
API requests require a Bearer token in the Authorization header:
curl -H "Authorization: Bearer your-api-key" http://localhost:8080/api/v2/pagesCreate API keys in the admin panel under Settings > API Keys.
The REST surface is /api/v2. The OpenAPI 3.1 spec is generated from Go types via huma and served live at /api/v2/openapi.json (or .yaml). Swagger UI is available at /api/v2/docs.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v2/status |
API status | Public |
| GET | /api/v2/auth |
Inspect the calling API key | Required |
| GET | /api/v2/pages |
List pages | Optional |
| GET | /api/v2/pages/{id} |
Get page by ID | Optional |
| GET | /api/v2/pages/slug/{slug} |
Get page by slug | Optional |
| POST | /api/v2/pages |
Create page | Required |
| PUT | /api/v2/pages/{id} |
Update page | Required |
| DELETE | /api/v2/pages/{id} |
Delete page | Required |
| GET | /api/v2/media |
List media | Optional |
| GET | /api/v2/media/{id} |
Get media by ID | Optional |
| POST | /api/v2/media |
Upload a single file | Required |
| POST | /api/v2/media/batch |
Upload multiple files | Required |
| PUT | /api/v2/media/{id} |
Update media metadata | Required |
| DELETE | /api/v2/media/{id} |
Delete media | Required |
| GET | /api/v2/tags |
List tags | Public |
| POST | /api/v2/tags |
Create tag | Required |
| PUT | /api/v2/tags/{id} |
Update tag | Required |
| DELETE | /api/v2/tags/{id} |
Delete tag | Required |
| GET | /api/v2/categories |
List categories (tree or flat) | Public |
| POST | /api/v2/categories |
Create category | Required |
| PUT | /api/v2/categories/{id} |
Update category | Required |
| DELETE | /api/v2/categories/{id} |
Delete category | Required |
| GET | /api/v2/openapi.json |
OpenAPI 3.1 spec (JSON) | Public |
| GET | /api/v2/openapi.yaml |
OpenAPI 3.1 spec (YAML) | Public |
| GET | /api/v2/docs |
Swagger UI | Public |
| GET | /health |
Health check | Public |
{
"data": { ... },
"meta": {
"total": 100,
"page": 1,
"per_page": 20
}
}{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": { "title": "Title is required" }
}
}Core themes (default, developer) are embedded in the binary. To create a custom theme, add a directory in custom/themes/:
custom/themes/my-theme/
├── theme.json # Theme configuration
├── templates/
│ ├── layouts/
│ │ └── base.html # Base layout
│ ├── pages/
│ │ ├── home.html # Homepage template
│ │ ├── page.html # Single page template
│ │ └── 404.html # Not found page
│ └── partials/
│ ├── header.html
│ └── footer.html
└── static/
├── css/
└── js/
To override an embedded theme, create a custom theme with the same name:
custom/themes/default/ # Overrides the embedded 'default' theme
Custom themes with the same name as core themes take priority.
{
"name": "My Theme",
"version": "1.0.0",
"author": "Your Name",
"description": "A custom theme",
"settings": [
{
"key": "primary_color",
"label": "Primary Color",
"type": "color",
"default": "#3b82f6"
}
]
}Create custom modules to extend functionality:
package mymodule
import (
"database/sql"
"embed"
"github.com/olegiv/ocms-go/internal/module"
"github.com/go-chi/chi/v5"
)
//go:embed locales
var localesFS embed.FS
type MyModule struct {
module.BaseModule
}
func New() *MyModule {
return &MyModule{
BaseModule: module.NewBaseModule("mymodule", "1.0.0", "My custom module"),
}
}
func (m *MyModule) RegisterRoutes(r chi.Router) {
r.Get("/my-endpoint", m.handleEndpoint)
}
func (m *MyModule) Migrations() []module.Migration {
return []module.Migration{
{
Version: 1,
Description: "Create my_table",
Up: func(db *sql.DB) error {
_, err := db.Exec("CREATE TABLE my_table (...)")
return err
},
},
}
}
// TranslationsFS returns embedded locale files for i18n support
func (m *MyModule) TranslationsFS() embed.FS {
return localesFS
}Add self-registration in register.go:
package mymodule
import "github.com/olegiv/ocms-go/internal/module"
func init() {
module.RegisterCustomModule(New())
}Then add a blank import to custom/modules/imports.go:
_ "github.com/olegiv/ocms-go/custom/modules/mymodule"See docs/custom-modules.md for the full guide and custom/modules/bookmarks/ for a working example.
Modules can embed their own translation files:
mymodule/
├── module.go
├── handlers.go
└── locales/
├── en/
│ └── messages.json
└── ru/
└── messages.json
Translation keys should be prefixed with the module name (e.g., mymodule.title).
Modules can be enabled/disabled from the admin UI at Admin > Modules. When a module is disabled:
- Public routes return 404
- Admin routes redirect to the modules list
- Template functions are not registered
- The module remains initialized but inactive
Run all tests:
OCMS_SESSION_SECRET=test-secret-key-32-bytes-long!! go test ./...Run tests with verbose output:
OCMS_SESSION_SECRET=test-secret-key-32-bytes-long!! go test -v ./...Run tests for a specific package:
OCMS_SESSION_SECRET=test-secret-key-32-bytes-long!! go test -v ./internal/store/...Check for vulnerabilities:
govulncheck ./...Project documentation is published at the GitHub Wiki and managed as a git submodule at wiki/.
# Edit a page
vim wiki/Getting-Started.md
# Publish changes
/update-wiki
# Or manually:
cd wiki
git add -A && git commit -m "Update page" && git push
cd ..
git add wiki
git commit -m "Update wiki submodule"git clone --recurse-submodules https://github.com/olegiv/ocms-go.gitThis project uses a shared submodule at .claude/shared containing reusable Claude Code extensions:
- Agents: security-auditor, project-architect, code-quality-auditor
- Commands: commit-prepare, commit-do, security-audit, setup-project-tools
- Global config: CLAUDE.md rules and settings.json templates
To update the shared Claude Code tools to the latest version:
# Using the slash command (recommended)
/update-submodule
# Or manually
git submodule update --remote --mergeIf you are using Codex, Claude slash commands are not registered in the
Codex UI and may show No commands. Use shell/make commands directly,
or ask Codex in chat to run them.
You can run slash-command equivalents through Claude CLI:
claude -p "/commit-prepare" --dangerously-skip-permissions
claude -p "/commit-do" --dangerously-skip-permissions
claude -p "/code-quality" --dangerously-skip-permissions
claude -p "/security-audit" --dangerously-skip-permissionsCodex wrapper commands (Claude proxy by default):
./scripts/codex-commands code-quality
./scripts/codex-commands security-audit
./scripts/codex-commands commit-prepare
./scripts/codex-commands commit-do
# Explicit local fallback scripts
./scripts/codex-commands code-quality-local
./scripts/codex-commands security-audit-local
./scripts/codex-commands commit-prepare-local
./scripts/codex-commands commit-do-localAfter updating, stage and commit the submodule change if you want to keep it:
git add .claude/shared
git commit -m "Update Claude Code shared submodule"- Backend: Go 1.26+
- Database: SQLite with goose migrations
- SQL: Type-safe queries with sqlc
- Templates: templ for type-safe HTML
- Frontend: HTMX + Alpine.js
- Rich Text Editor: TinyMCE for content editing
- Styling: Custom SCSS framework
- Authentication: Secure sessions with argon2id password hashing
- Containerization: Docker with multi-stage builds
Copyright (C) 2025-2026 Oleg Ivanchenko
GNU General Public License v3.0 - see LICENSE file for details.