From waggle
Creates, lists, deletes, and regenerates custom task visualizations. Use this skill whenever the user wants to create, delete, customize, or manage saved task visualizations — custom dashboards, filtered boards, or personalized views. Triggers on: "create view", "custom view", "make a dashboard", "view of", "delete view", "list views", "regenerate view", "my view", "build a board", "design visualization".
npx claudepluginhub kazukinagata/waggle --plugin waggleThis skill uses the workspace's default tool permissions.
You manage custom task visualizations that users can create via natural language.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
You manage custom task visualizations that users can create via natural language.
Invoke the bootstrap-session skill to establish the active provider and current user.
Skip if active_provider and current_user are already set in this conversation.
Custom views are stored outside the plugin directory so they survive updates:
mkdir -p ~/.waggle/views
When the user asks to create a custom view (e.g., "create a view showing blocked tasks by assignee", "make a dashboard for team progress"):
team-progress-dashboard)~/.waggle/views/<slug>.htmlhttp://localhost:3456/custom/<slug>.htmlWhen the user asks to list custom views:
ls ~/.waggle/views/*.html 2>/dev/null
Or use the API: curl -s http://localhost:3456/api/views | jq
When the user asks to delete a custom view:
rm ~/.waggle/views/<slug>.html
Confirm deletion with the user before removing.
When the user asks to regenerate or update a custom view:
~/.waggle/views/<slug>.htmlWhen generating a custom view HTML file, use this skeleton. Fill in the visualization logic based on the user's request.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-name" content="VIEW_NAME_HERE">
<meta name="view-description" content="VIEW_DESCRIPTION_HERE">
<title>VIEW_NAME_HERE — Waggle</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
surface: { primary: 'rgba(28,28,30,0.8)', secondary: 'rgba(44,44,46,0.6)', tertiary: 'rgba(58,58,60,0.4)' },
label: { primary: '#ffffff', secondary: 'rgba(235,235,245,0.6)', tertiary: 'rgba(235,235,245,0.3)' },
accent: { blue: '#0a84ff', green: '#30d158', orange: '#ff9f0a', red: '#ff453a', purple: '#bf5af2', indigo: '#5e5ce6' },
}
}
}
}
</script>
<style>
body { background: #000; font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif; -webkit-font-smoothing: antialiased; }
</style>
</head>
<body class="min-h-screen text-white">
<!-- Header -->
<header class="sticky top-0 z-50 backdrop-blur-xl bg-black/80 border-b border-white/10">
<div class="max-w-7xl mx-auto px-6 h-14 flex items-center justify-between">
<div class="flex items-center gap-4">
<a href="/selector.html" class="text-label-secondary hover:text-white transition text-sm">← Views</a>
<h1 class="text-base font-semibold">VIEW_NAME_HERE</h1>
</div>
<div class="flex items-center gap-3">
<span id="sse-status" class="text-xs px-2 py-1 rounded-full bg-surface-secondary text-label-tertiary">Connecting...</span>
</div>
</div>
</header>
<!-- Main content -->
<main id="app" class="max-w-7xl mx-auto px-6 py-8">
<p class="text-label-secondary">Loading tasks...</p>
</main>
<script>
let tasks = [];
// Check for static mode
if (window.__STATIC_DATA__) {
tasks = window.__STATIC_DATA__.tasks || [];
document.getElementById('sse-status').textContent = 'Static';
document.getElementById('sse-status').classList.add('bg-accent-orange/20', 'text-accent-orange');
render();
} else {
// Fetch initial data
fetch('/api/tasks')
.then(r => r.json())
.then(data => {
tasks = data.tasks || [];
render();
})
.catch(() => {
document.getElementById('app').innerHTML = '<p class="text-accent-red">Failed to load tasks.</p>';
});
// SSE for real-time updates
const es = new EventSource('/api/events');
es.addEventListener('connected', () => {
const el = document.getElementById('sse-status');
el.textContent = 'Live';
el.className = 'text-xs px-2 py-1 rounded-full bg-accent-green/20 text-accent-green';
});
es.addEventListener('refresh', (e) => {
const data = JSON.parse(e.data);
tasks = data.tasks || [];
render();
});
es.onerror = () => {
const el = document.getElementById('sse-status');
el.textContent = 'Disconnected';
el.className = 'text-xs px-2 py-1 rounded-full bg-accent-red/20 text-accent-red';
};
}
// Click-to-copy task ID
document.getElementById('app').addEventListener('click', (e) => {
const taskEl = e.target.closest('[data-task-id]');
if (taskEl) {
navigator.clipboard.writeText(taskEl.dataset.taskId);
const orig = taskEl.style.outline;
taskEl.style.outline = '2px solid rgba(10,132,255,0.5)';
setTimeout(() => { taskEl.style.outline = orig; }, 600);
}
});
function render() {
const app = document.getElementById('app');
// ===== CUSTOM VISUALIZATION LOGIC HERE =====
// Use the `tasks` array. Each task has these fields:
// See "Task Data Shape" below for the full interface.
app.innerHTML = '<p class="text-label-secondary">Implement render() for this view.</p>';
}
</script>
</body>
</html>
Each task object in the tasks array has these fields:
interface Task {
id: string;
title: string;
description: string;
acceptanceCriteria: string;
status: "Backlog" | "Ready" | "In Progress" | "In Review" | "Done" | "Blocked" | "Cancelled";
blockedBy: string[];
priority: "Urgent" | "High" | "Medium" | "Low";
executor: string; // "cli" | "claude-desktop" | "cowork" | "human" | custom
requiresReview: boolean;
executionPlan: string;
workingDirectory: string;
sessionReference: string;
dispatchedAt: string | null;
agentOutput: string;
errorMessage: string;
context: string;
artifacts: string;
repository: string | null;
dueDate: string | null;
tags: string[];
parentTaskId: string | null;
project: string | null;
team: string | null;
assignee: { id: string; name: string }[];
acknowledgedAt: string | null;
createdAt: string | null;
url: string;
}
Derive slugs from the user's description:
team-progress-dashboardblocked-tasks-by-assigneepriority-heatmapUse lowercase, hyphens for spaces, remove special characters.
/selector.html