
Hermes Marketing Dashboard
- 968 installs
- 6 repo stars
- Updated July 18, 2026
- aradotso/marketing-skills
hermes-marketing-dashboard is an open-source agent skill that deploys a marketing operations control center connecting CRM, outreach, content workflows, and analytics with OpenClaw and SQLite for developers running AI-dr
About
hermes-marketing-dashboard is an open-source marketing operations control center skill from the ara.so Marketing Skills collection. It integrates CRM, outreach, content operations, and analytics for AI agent teams using OpenClaw plus SQLite as the data layer. The skill lists eight trigger phrases including set up hermes marketing dashboard, configure openclaw marketing operations, and deploy hermes dashboard with sqlite. Use hermes-marketing-dashboard when you need a unified marketing ops surface for agent-driven campaigns rather than disconnected spreadsheets and ad hoc scripts. The deliverable is a deployable dashboard wiring CRM records, outreach sequences, content workflows, and analytics into one agent-accessible system.
- Unified dashboard combining CRM, outreach sequencing, content operations, and analytics
- Native OpenClaw integration for AI agent marketing automation
- Local-first SQLite storage with zero external SaaS dependency
- Next.js application with built-in authentication and environment bootstrapping
- Supports full marketing automation workflows for AI-powered teams
Hermes Marketing Dashboard by the numbers
- 968 all-time installs (skills.sh)
- +4 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #450 of 1,881 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aradotso/marketing-skills --skill hermes-marketing-dashboardAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 968 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 18, 2026 |
| Repository | aradotso/marketing-skills ↗ |
How do you build an AI marketing ops dashboard?
Run a unified marketing operations control center that connects CRM, outreach, content workflows, and analytics for their AI agent team.
Who is it for?
Developers building AI agent marketing stacks who need one OpenClaw-connected control center for CRM, outreach, content ops, and analytics.
Skip if: Engineering teams that only need a static landing page or SEO copy without CRM, outreach automation, or agent-coordinated marketing operations.
When should I use this skill?
A developer asks to set up Hermes marketing dashboard, configure OpenClaw marketing operations, or build an AI agent marketing control center with SQLite.
What you get
Deployed Hermes dashboard with OpenClaw integration, SQLite datastore, and connected CRM, outreach, content, and analytics modules.
- marketing dashboard deployment
- CRM and outreach integration
By the numbers
- Skill documents 8 trigger phrases for Hermes and OpenClaw marketing setup
- Integrates 4 operations modules: CRM, outreach, content ops, and analytics
Files
Hermes Marketing Dashboard
Skill by ara.so — Marketing Skills collection.
Hermes Dashboard is an open-source marketing operations control center designed for AI agent teams. It provides CRM, outreach sequencing, content operations, analytics, and automation workflows in a single Next.js application powered by OpenClaw integration and local SQLite storage.
Installation
Prerequisites
- Node.js 18+
- pnpm (required) - install with
npm install -g pnpmorcorepack enable - OpenClaw CLI (optional but recommended for agent integration)
Quick Start
git clone https://github.com/builderz-labs/marketing-dashboard.git
cd marketing-dashboard
pnpm install
pnpm env:bootstrap
pnpm devThe application will start at http://localhost:3000.
Configuration
Required Environment Variables
Create a .env.local file:
# Authentication (required)
AUTH_USER=admin
AUTH_PASS=your-secure-password-min-10-chars
API_KEY=your-api-key-for-programmatic-access
# Cookie security (false for HTTP local, true for HTTPS production)
AUTH_COOKIE_SECURE=false
# Database (auto-created in ./state)
DATABASE_URL=./state/hermes.dbOpenClaw Integration
For AI agent integration with OpenClaw:
# OpenClaw home directory
HERMES_OPENCLAW_HOME=/path/to/openclaw
# Default instance name
HERMES_DEFAULT_INSTANCE=main
# Multi-instance support (optional JSON array)
HERMES_OPENCLAW_INSTANCES='[{"name":"prod","path":"/openclaw/prod"},{"name":"dev","path":"/openclaw/dev"}]'Security Configuration
# Host access lock (default: local-only)
HERMES_HOST_LOCK=local # or 'off' or 'host1,host2'
# Writeback protection (keep false unless explicitly needed)
HERMES_ALLOW_POLICY_WRITE=false
HERMES_ALLOW_CRON_WRITE=false
HERMES_ALLOW_WORKSPACE_WRITE=falseOptional Analytics Integration
# Plausible Analytics
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=yourdomain.com
PLAUSIBLE_API_KEY=your-plausible-api-key
# Google Analytics 4
NEXT_PUBLIC_GA4_ID=G-XXXXXXXXXX1Password Runtime Overlay (Optional)
HERMES_1PASSWORD_MODE=auto # off|auto|required
HERMES_OP_ENV_FILE=/etc/hermes-dashboard/hermes-dashboard.op.envKey Commands
Development
# Start development server
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Type checking
pnpm typecheck
# Linting
pnpm lint
# Run tests
pnpm test
# Run end-to-end tests
pnpm test:e2eDatabase Management
# Bootstrap environment and database
pnpm env:bootstrap
# Reset database (warning: destructive)
pnpm db:resetTemplate Export
# Audit template for sensitive data
./scripts/template-audit.sh
# Export clean template
./scripts/template-export.sh /path/to/outputCore API Patterns
CRM Lead Management
// app/api/crm/leads/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getDatabase } from '@/lib/db';
export async function GET(request: NextRequest) {
const db = getDatabase();
const leads = db.prepare(`
SELECT id, email, name, source, status, created_at
FROM crm_leads
WHERE status = ?
ORDER BY created_at DESC
`).all('active');
return NextResponse.json({ leads });
}
export async function POST(request: NextRequest) {
const db = getDatabase();
const { email, name, source, metadata } = await request.json();
const result = db.prepare(`
INSERT INTO crm_leads (email, name, source, metadata, status)
VALUES (?, ?, ?, ?, 'new')
`).run(email, name, source, JSON.stringify(metadata));
return NextResponse.json({ id: result.lastInsertRowid }, { status: 201 });
}Outreach Sequencing
// lib/outreach/sequence.ts
import { getDatabase } from '@/lib/db';
export interface OutreachSequence {
id: number;
name: string;
steps: OutreachStep[];
status: 'active' | 'paused' | 'archived';
}
export interface OutreachStep {
order: number;
delay_hours: number;
template_id: string;
channel: 'email' | 'linkedin' | 'twitter';
}
export function createSequence(
name: string,
steps: OutreachStep[]
): number {
const db = getDatabase();
const result = db.prepare(`
INSERT INTO outreach_sequences (name, steps, status)
VALUES (?, ?, 'active')
`).run(name, JSON.stringify(steps));
return result.lastInsertRowid as number;
}
export function enrollInSequence(
leadId: number,
sequenceId: number
): void {
const db = getDatabase();
db.prepare(`
INSERT INTO outreach_enrollments (lead_id, sequence_id, current_step, status)
VALUES (?, ?, 0, 'active')
`).run(leadId, sequenceId);
}
export function pauseSequence(sequenceId: number): void {
const db = getDatabase();
db.prepare(`
UPDATE outreach_sequences
SET status = 'paused'
WHERE id = ?
`).run(sequenceId);
}Content Operations
// lib/content/calendar.ts
import { getDatabase } from '@/lib/db';
export interface ContentItem {
id: number;
title: string;
type: 'blog' | 'social' | 'email' | 'video';
status: 'draft' | 'scheduled' | 'published';
scheduled_at?: Date;
published_at?: Date;
metadata: Record<string, any>;
}
export function getContentCalendar(
startDate: Date,
endDate: Date
): ContentItem[] {
const db = getDatabase();
const items = db.prepare(`
SELECT id, title, type, status, scheduled_at, published_at, metadata
FROM content_items
WHERE scheduled_at BETWEEN ? AND ?
ORDER BY scheduled_at ASC
`).all(startDate.toISOString(), endDate.toISOString());
return items.map(item => ({
...item,
metadata: JSON.parse(item.metadata as string),
scheduled_at: item.scheduled_at ? new Date(item.scheduled_at) : undefined,
published_at: item.published_at ? new Date(item.published_at) : undefined,
}));
}
export function createContentItem(item: Omit<ContentItem, 'id'>): number {
const db = getDatabase();
const result = db.prepare(`
INSERT INTO content_items (title, type, status, scheduled_at, metadata)
VALUES (?, ?, ?, ?, ?)
`).run(
item.title,
item.type,
item.status,
item.scheduled_at?.toISOString(),
JSON.stringify(item.metadata)
);
return result.lastInsertRowid as number;
}OpenClaw Agent Integration
// lib/openclaw/agents.ts
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
export interface OpenClawAgent {
name: string;
description: string;
type: 'agent' | 'squad';
capabilities: string[];
config: Record<string, any>;
}
export async function discoverAgents(
openclawHome: string
): Promise<OpenClawAgent[]> {
const agentsDir = join(openclawHome, 'agents');
const agents: OpenClawAgent[] = [];
try {
const entries = await readdir(agentsDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const configPath = join(agentsDir, entry.name, 'agent.json');
try {
const configData = await readFile(configPath, 'utf-8');
const config = JSON.parse(configData);
agents.push({
name: entry.name,
description: config.description || '',
type: config.type || 'agent',
capabilities: config.capabilities || [],
config,
});
} catch (err) {
console.warn(`Could not load agent config for ${entry.name}`);
}
}
}
} catch (err) {
console.error('Failed to discover agents:', err);
}
return agents;
}Cron Job Management
// lib/cron/scheduler.ts
import { getDatabase } from '@/lib/db';
export interface CronJob {
id: number;
name: string;
schedule: string; // cron expression, 'every 1h', or 'at 09:00'
agent: string;
task: string;
enabled: boolean;
last_run?: Date;
}
export function createCronJob(job: Omit<CronJob, 'id'>): number {
const db = getDatabase();
const result = db.prepare(`
INSERT INTO cron_jobs (name, schedule, agent, task, enabled)
VALUES (?, ?, ?, ?, ?)
`).run(job.name, job.schedule, job.agent, job.task, job.enabled ? 1 : 0);
return result.lastInsertRowid as number;
}
export function getCronJobs(): CronJob[] {
const db = getDatabase();
const jobs = db.prepare(`
SELECT id, name, schedule, agent, task, enabled, last_run
FROM cron_jobs
ORDER BY name ASC
`).all();
return jobs.map(job => ({
...job,
enabled: Boolean(job.enabled),
last_run: job.last_run ? new Date(job.last_run) : undefined,
}));
}
export function updateLastRun(jobId: number): void {
const db = getDatabase();
db.prepare(`
UPDATE cron_jobs
SET last_run = ?
WHERE id = ?
`).run(new Date().toISOString(), jobId);
}Authentication Patterns
Session-Based Auth
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('hermes-session');
if (!session && request.nextUrl.pathname.startsWith('/api/')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};API Key Auth
// lib/auth/api-key.ts
import { NextRequest } from 'next/server';
export function validateApiKey(request: NextRequest): boolean {
const authHeader = request.headers.get('authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false;
}
const token = authHeader.substring(7);
return token === process.env.API_KEY;
}
// Usage in API route
export async function GET(request: NextRequest) {
if (!validateApiKey(request)) {
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
}
// Continue with request...
}Analytics Integration
Track Events
// lib/analytics/events.ts
import { getDatabase } from '@/lib/db';
export interface AnalyticsEvent {
event_type: string;
properties: Record<string, any>;
user_id?: string;
session_id?: string;
}
export function trackEvent(event: AnalyticsEvent): void {
const db = getDatabase();
db.prepare(`
INSERT INTO analytics_events (event_type, properties, user_id, session_id, timestamp)
VALUES (?, ?, ?, ?, ?)
`).run(
event.event_type,
JSON.stringify(event.properties),
event.user_id,
event.session_id,
new Date().toISOString()
);
}
export function getKPIs(startDate: Date, endDate: Date) {
const db = getDatabase();
return {
leads: db.prepare(`
SELECT COUNT(*) as count FROM crm_leads
WHERE created_at BETWEEN ? AND ?
`).get(startDate.toISOString(), endDate.toISOString()),
outreach_sent: db.prepare(`
SELECT COUNT(*) as count FROM outreach_messages
WHERE sent_at BETWEEN ? AND ?
`).get(startDate.toISOString(), endDate.toISOString()),
content_published: db.prepare(`
SELECT COUNT(*) as count FROM content_items
WHERE published_at BETWEEN ? AND ?
`).get(startDate.toISOString(), endDate.toISOString()),
};
}Component Patterns
Dashboard Widget
// components/dashboard/LeadsFunnel.tsx
'use client';
import { useEffect, useState } from 'react';
interface FunnelData {
stage: string;
count: number;
}
export function LeadsFunnel() {
const [data, setData] = useState<FunnelData[]>([]);
useEffect(() => {
fetch('/api/crm/funnel')
.then(res => res.json())
.then(setData);
}, []);
return (
<div className="funnel-widget">
<h3>Pipeline Funnel</h3>
{data.map(stage => (
<div key={stage.stage} className="funnel-stage">
<span>{stage.stage}</span>
<span>{stage.count}</span>
</div>
))}
</div>
);
}Troubleshooting
Database Lock Errors
SQLite database locks can occur with concurrent writes:
// lib/db.ts
import Database from 'better-sqlite3';
let db: Database.Database | null = null;
export function getDatabase(): Database.Database {
if (!db) {
db = new Database(process.env.DATABASE_URL || './state/hermes.db');
db.pragma('journal_mode = WAL'); // Write-Ahead Logging for better concurrency
db.pragma('busy_timeout = 5000'); // 5 second timeout
}
return db;
}OpenClaw Agent Discovery Fails
Ensure HERMES_OPENCLAW_HOME points to valid directory:
# Verify path
ls $HERMES_OPENCLAW_HOME/agents
# Check permissions
chmod -R u+r $HERMES_OPENCLAW_HOME/agentsSession Cookie Not Persisting
For HTTPS deployments, ensure:
AUTH_COOKIE_SECURE=trueFor local HTTP development:
AUTH_COOKIE_SECURE=falseHost Lock Blocking Access
If you need to access from network:
# Disable (not recommended for production)
HERMES_HOST_LOCK=off
# Allow specific hosts
HERMES_HOST_LOCK=localhost,192.168.1.100,mydomain.comAPI Authentication Failures
Verify API key in request headers:
curl -H "Authorization: Bearer $API_KEY" http://localhost:3000/api/crm/leadsCheck environment variable is loaded:
// In API route
if (!process.env.API_KEY) {
console.error('API_KEY not configured');
}Production Deployment
Security Checklist
1. Change default credentials:
AUTH_USER=your-admin-username
AUTH_PASS=strong-password-min-10-chars
API_KEY=cryptographically-secure-key2. Enable HTTPS cookie security:
AUTH_COOKIE_SECURE=true3. Keep host lock enabled:
HERMES_HOST_LOCK=yourdomain.com4. Keep writeback disabled unless required:
HERMES_ALLOW_POLICY_WRITE=false
HERMES_ALLOW_CRON_WRITE=false
HERMES_ALLOW_WORKSPACE_WRITE=falseBuild and Deploy
pnpm build
pnpm startFor Docker deployment:
FROM node:18-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
EXPOSE 3000
CMD ["pnpm", "start"]Common Patterns
Multi-Instance OpenClaw Setup
// lib/openclaw/instances.ts
export function getInstances(): Array<{name: string, path: string}> {
const instancesEnv = process.env.HERMES_OPENCLAW_INSTANCES;
if (instancesEnv) {
return JSON.parse(instancesEnv);
}
return [{
name: process.env.HERMES_DEFAULT_INSTANCE || 'main',
path: process.env.HERMES_OPENCLAW_HOME || './openclaw'
}];
}Audit Logging
// lib/audit/logger.ts
import { getDatabase } from '@/lib/db';
export function logAuditEvent(
action: string,
userId: string,
metadata: Record<string, any>
): void {
const db = getDatabase();
db.prepare(`
INSERT INTO audit_log (action, user_id, metadata, timestamp)
VALUES (?, ?, ?, ?)
`).run(action, userId, JSON.stringify(metadata), new Date().toISOString());
}Related skills
How it compares
Use hermes-marketing-dashboard for agent-coordinated marketing ops; use single-purpose SEO or copy skills when you only need content generation without CRM and outreach wiring.
FAQ
What does hermes-marketing-dashboard connect?
hermes-marketing-dashboard unifies CRM, outreach, content operations, and analytics into one open-source control center designed for AI agent teams using OpenClaw with SQLite storage.
Which stack powers hermes-marketing-dashboard?
hermes-marketing-dashboard runs as an open-source marketing operations center built on OpenClaw for agent coordination and SQLite for local datastore persistence across CRM and campaign modules.