clamper-ai 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRD.md +416 -0
- package/README.md +156 -0
- package/bin/clamper.mjs +66 -0
- package/dashboard/app.js +287 -0
- package/dashboard/index.html +137 -0
- package/dashboard/style.css +121 -0
- package/package.json +24 -0
- package/skills/clawkit-sync/SKILL.md +160 -0
- package/skills/code-mentor/SKILL.md +85 -0
- package/skills/csv-pipeline/SKILL.md +82 -0
- package/skills/developer/SKILL.md +57 -0
- package/skills/image/SKILL.md +71 -0
- package/skills/json/SKILL.md +59 -0
- package/skills/productivity/SKILL.md +68 -0
- package/skills/quick-reminders/SKILL.md +70 -0
- package/skills/svg-draw/SKILL.md +75 -0
- package/skills/weather/SKILL.md +72 -0
- package/skills/workspace-manager/SKILL.md +69 -0
- package/src/cron.mjs +30 -0
- package/src/dashboard.mjs +69 -0
- package/src/doctor.mjs +69 -0
- package/src/init.mjs +145 -0
- package/src/log-activity.mjs +26 -0
- package/src/scaffold.mjs +57 -0
- package/src/skills.mjs +52 -0
- package/src/status.mjs +43 -0
- package/src/sync.mjs +585 -0
- package/src/update.mjs +100 -0
- package/src/upgrade.mjs +104 -0
- package/src/utils.mjs +67 -0
- package/src/validate.mjs +66 -0
- package/templates/AGENTS.md +77 -0
- package/templates/HEARTBEAT.md +12 -0
- package/templates/IDENTITY.md +7 -0
- package/templates/LEARNINGS.md +14 -0
- package/templates/MEMORY.md +14 -0
- package/templates/PROTOCOL_COST_EFFICIENCY.md +30 -0
- package/templates/SKILLS-INDEX.md +21 -0
- package/templates/SOUL.md +22 -0
- package/templates/TOOLS.md +14 -0
- package/templates/USER.md +5 -0
- package/templates/memory/knowledge/about-me.md +13 -0
- package/templates/scripts/nightly-consolidation.sh +54 -0
- package/templates/tasks/QUEUE.md +10 -0
package/dashboard/app.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// Clamper Dashboard — Generic Agent Dashboard
|
|
2
|
+
(function() {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_WS = 'ws://localhost:18789';
|
|
6
|
+
let ws = null;
|
|
7
|
+
let tier = 'free'; // 'free' or 'pro'
|
|
8
|
+
let connected = false;
|
|
9
|
+
|
|
10
|
+
// --- Config ---
|
|
11
|
+
function getConfig() {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(localStorage.getItem('clamper_dash') || '{}');
|
|
14
|
+
} catch { return {}; }
|
|
15
|
+
}
|
|
16
|
+
function saveConfig(c) { localStorage.setItem('clamper_dash', JSON.stringify(c)); }
|
|
17
|
+
function getWsUrl() { return getConfig().wsUrl || DEFAULT_WS; }
|
|
18
|
+
|
|
19
|
+
// --- Tier ---
|
|
20
|
+
function loadTier() {
|
|
21
|
+
const cfg = getConfig();
|
|
22
|
+
if (cfg.proKey && cfg.proKey.startsWith('clk_pro_')) {
|
|
23
|
+
tier = 'pro';
|
|
24
|
+
} else {
|
|
25
|
+
tier = 'free';
|
|
26
|
+
}
|
|
27
|
+
updateTierUI();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function updateTierUI() {
|
|
31
|
+
const badge = document.getElementById('tierBadge');
|
|
32
|
+
badge.textContent = tier.toUpperCase();
|
|
33
|
+
badge.style.color = tier === 'pro' ? '#ffcc00' : '#8888a0';
|
|
34
|
+
|
|
35
|
+
const isPro = tier === 'pro';
|
|
36
|
+
// Chat
|
|
37
|
+
const chatInput = document.getElementById('chatInputWrap');
|
|
38
|
+
const chatGate = document.getElementById('chatProGate');
|
|
39
|
+
chatInput.style.display = isPro ? 'flex' : 'none';
|
|
40
|
+
chatGate.style.display = isPro ? 'none' : 'block';
|
|
41
|
+
document.getElementById('chatInput').disabled = !isPro || !connected;
|
|
42
|
+
document.getElementById('chatSend').disabled = !isPro || !connected;
|
|
43
|
+
|
|
44
|
+
// Tasks
|
|
45
|
+
const taskInput = document.getElementById('taskInputWrap');
|
|
46
|
+
const taskGate = document.getElementById('taskProGate');
|
|
47
|
+
taskInput.style.display = isPro ? 'flex' : 'none';
|
|
48
|
+
taskGate.style.display = isPro ? 'none' : 'block';
|
|
49
|
+
document.getElementById('taskInput').disabled = !isPro || !connected;
|
|
50
|
+
document.getElementById('taskAdd').disabled = !isPro || !connected;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Tabs ---
|
|
54
|
+
document.querySelectorAll('.tab').forEach(tab => {
|
|
55
|
+
tab.addEventListener('click', () => {
|
|
56
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
57
|
+
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
|
58
|
+
tab.classList.add('active');
|
|
59
|
+
document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// --- Settings ---
|
|
64
|
+
document.getElementById('settingsBtn').addEventListener('click', () => {
|
|
65
|
+
const cfg = getConfig();
|
|
66
|
+
document.getElementById('wsUrlInput').value = cfg.wsUrl || '';
|
|
67
|
+
document.getElementById('proKeyInput').value = cfg.proKey || '';
|
|
68
|
+
document.getElementById('settingsModal').style.display = 'flex';
|
|
69
|
+
});
|
|
70
|
+
document.getElementById('closeSettings').addEventListener('click', () => {
|
|
71
|
+
document.getElementById('settingsModal').style.display = 'none';
|
|
72
|
+
});
|
|
73
|
+
document.getElementById('saveSettings').addEventListener('click', () => {
|
|
74
|
+
const cfg = getConfig();
|
|
75
|
+
const newUrl = document.getElementById('wsUrlInput').value.trim();
|
|
76
|
+
const newKey = document.getElementById('proKeyInput').value.trim();
|
|
77
|
+
cfg.wsUrl = newUrl || '';
|
|
78
|
+
cfg.proKey = newKey || '';
|
|
79
|
+
saveConfig(cfg);
|
|
80
|
+
document.getElementById('settingsModal').style.display = 'none';
|
|
81
|
+
loadTier();
|
|
82
|
+
connectWS();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// --- WebSocket ---
|
|
86
|
+
function setConnected(state) {
|
|
87
|
+
connected = state;
|
|
88
|
+
const ind = document.getElementById('wsIndicator');
|
|
89
|
+
const lbl = document.getElementById('wsLabel');
|
|
90
|
+
const sub = document.getElementById('connectionStatus');
|
|
91
|
+
if (state) {
|
|
92
|
+
ind.classList.add('live');
|
|
93
|
+
lbl.textContent = 'LIVE';
|
|
94
|
+
sub.textContent = 'Connected to ' + getWsUrl();
|
|
95
|
+
} else {
|
|
96
|
+
ind.classList.remove('live');
|
|
97
|
+
lbl.textContent = 'OFFLINE';
|
|
98
|
+
sub.textContent = 'Disconnected';
|
|
99
|
+
}
|
|
100
|
+
updateTierUI();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function connectWS() {
|
|
104
|
+
if (ws) { try { ws.close(); } catch {} }
|
|
105
|
+
const url = getWsUrl();
|
|
106
|
+
document.getElementById('connectionStatus').textContent = 'Connecting to ' + url + '...';
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
ws = new WebSocket(url);
|
|
110
|
+
} catch {
|
|
111
|
+
setConnected(false);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
ws.onopen = () => {
|
|
116
|
+
setConnected(true);
|
|
117
|
+
addFeedItem('Connected to agent');
|
|
118
|
+
// Request initial status
|
|
119
|
+
wsSend({ type: 'dashboard_status' });
|
|
120
|
+
wsSend({ type: 'file_read', path: 'tasks/QUEUE.md' });
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
ws.onclose = () => {
|
|
124
|
+
setConnected(false);
|
|
125
|
+
addFeedItem('Connection lost');
|
|
126
|
+
setTimeout(() => { if (!connected) connectWS(); }, 5000);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
ws.onerror = () => {
|
|
130
|
+
setConnected(false);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
ws.onmessage = (ev) => {
|
|
134
|
+
try {
|
|
135
|
+
const msg = JSON.parse(ev.data);
|
|
136
|
+
handleMessage(msg);
|
|
137
|
+
} catch {}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function wsSend(obj) {
|
|
142
|
+
if (ws && ws.readyState === 1) {
|
|
143
|
+
ws.send(JSON.stringify(obj));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- Message Handler ---
|
|
148
|
+
function handleMessage(msg) {
|
|
149
|
+
switch (msg.type) {
|
|
150
|
+
case 'status':
|
|
151
|
+
case 'dashboard_status':
|
|
152
|
+
updateDashboardStats(msg);
|
|
153
|
+
break;
|
|
154
|
+
case 'chat_message':
|
|
155
|
+
addChatMessage(msg.role || 'agent', msg.content || msg.text || '');
|
|
156
|
+
break;
|
|
157
|
+
case 'file_content':
|
|
158
|
+
if (msg.path && msg.path.includes('QUEUE')) {
|
|
159
|
+
renderTasks(msg.content || '');
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'event':
|
|
163
|
+
case 'activity':
|
|
164
|
+
addFeedItem(msg.text || msg.message || JSON.stringify(msg));
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
if (msg.text || msg.message) {
|
|
168
|
+
addFeedItem(msg.text || msg.message);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Dashboard Stats ---
|
|
174
|
+
function updateDashboardStats(data) {
|
|
175
|
+
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val || '—'; };
|
|
176
|
+
set('statAgentStatus', data.agentStatus || data.status || 'Online');
|
|
177
|
+
set('statModel', data.model || '—');
|
|
178
|
+
set('statUptime', data.uptime || '—');
|
|
179
|
+
set('statChannel', data.channel || '—');
|
|
180
|
+
set('statDailyNotes', data.dailyNotes ?? '—');
|
|
181
|
+
set('statKnowledge', data.knowledgeFiles ?? '—');
|
|
182
|
+
set('statMemory', data.hasMemory ? '✓' : '—');
|
|
183
|
+
set('statLearnings', data.hasLearnings ? '✓' : '—');
|
|
184
|
+
set('statSkillCount', data.skillCount ?? '—');
|
|
185
|
+
set('statTier', tier.toUpperCase());
|
|
186
|
+
set('statSkillsUpdated', data.skillsUpdated || '—');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Activity Feed ---
|
|
190
|
+
function addFeedItem(text) {
|
|
191
|
+
const feed = document.getElementById('activityFeed');
|
|
192
|
+
const empty = feed.querySelector('.feed-empty');
|
|
193
|
+
if (empty) empty.remove();
|
|
194
|
+
|
|
195
|
+
const now = new Date();
|
|
196
|
+
const time = now.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });
|
|
197
|
+
const item = document.createElement('div');
|
|
198
|
+
item.className = 'feed-item';
|
|
199
|
+
item.innerHTML = `<span class="feed-time">${time}</span><span class="feed-msg">${escapeHtml(text)}</span>`;
|
|
200
|
+
feed.appendChild(item);
|
|
201
|
+
feed.scrollTop = feed.scrollHeight;
|
|
202
|
+
|
|
203
|
+
// Keep last 50
|
|
204
|
+
while (feed.children.length > 50) feed.removeChild(feed.firstChild);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Chat ---
|
|
208
|
+
function addChatMessage(role, content) {
|
|
209
|
+
const box = document.getElementById('chatMessages');
|
|
210
|
+
const div = document.createElement('div');
|
|
211
|
+
div.className = 'chat-msg ' + (role === 'user' ? 'user' : 'agent');
|
|
212
|
+
const time = new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' });
|
|
213
|
+
div.innerHTML = `<div class="msg-meta">${role === 'user' ? 'You' : 'Agent'} · ${time}</div><div>${escapeHtml(content)}</div>`;
|
|
214
|
+
box.appendChild(div);
|
|
215
|
+
box.scrollTop = box.scrollHeight;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
document.getElementById('chatSend').addEventListener('click', sendChat);
|
|
219
|
+
document.getElementById('chatInput').addEventListener('keydown', (e) => {
|
|
220
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); }
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
function sendChat() {
|
|
224
|
+
const input = document.getElementById('chatInput');
|
|
225
|
+
const text = input.value.trim();
|
|
226
|
+
if (!text) return;
|
|
227
|
+
addChatMessage('user', text);
|
|
228
|
+
wsSend({ type: 'chat_message', content: text });
|
|
229
|
+
input.value = '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Tasks ---
|
|
233
|
+
function renderTasks(content) {
|
|
234
|
+
const box = document.getElementById('tasksList');
|
|
235
|
+
box.innerHTML = '';
|
|
236
|
+
if (!content.trim()) {
|
|
237
|
+
box.innerHTML = '<div class="feed-empty">No tasks found</div>';
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const lines = content.split('\n');
|
|
241
|
+
for (const line of lines) {
|
|
242
|
+
const trimmed = line.trim();
|
|
243
|
+
if (!trimmed) continue;
|
|
244
|
+
const match = trimmed.match(/^-\s*\[([ xX])\]\s*(.+)/);
|
|
245
|
+
const div = document.createElement('div');
|
|
246
|
+
if (match) {
|
|
247
|
+
const done = match[1] !== ' ';
|
|
248
|
+
div.className = 'task-item' + (done ? ' done' : '');
|
|
249
|
+
div.innerHTML = `<span class="task-check">${done ? '☑' : '☐'}</span><span class="task-text">${escapeHtml(match[2])}</span>`;
|
|
250
|
+
} else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
|
|
251
|
+
div.className = 'task-item';
|
|
252
|
+
div.innerHTML = `<span class="task-check">•</span><span class="task-text">${escapeHtml(trimmed.slice(2))}</span>`;
|
|
253
|
+
} else {
|
|
254
|
+
div.className = 'task-item';
|
|
255
|
+
div.innerHTML = `<span class="task-text" style="color:var(--text-dim)">${escapeHtml(trimmed)}</span>`;
|
|
256
|
+
}
|
|
257
|
+
box.appendChild(div);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
document.getElementById('refreshTasks').addEventListener('click', () => {
|
|
262
|
+
wsSend({ type: 'file_read', path: 'tasks/QUEUE.md' });
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
document.getElementById('taskAdd').addEventListener('click', addTask);
|
|
266
|
+
document.getElementById('taskInput').addEventListener('keydown', (e) => {
|
|
267
|
+
if (e.key === 'Enter') addTask();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
function addTask() {
|
|
271
|
+
const input = document.getElementById('taskInput');
|
|
272
|
+
const text = input.value.trim();
|
|
273
|
+
if (!text) return;
|
|
274
|
+
wsSend({ type: 'file_append', path: 'tasks/QUEUE.md', content: '\n- [ ] ' + text });
|
|
275
|
+
input.value = '';
|
|
276
|
+
setTimeout(() => wsSend({ type: 'file_read', path: 'tasks/QUEUE.md' }), 500);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Helpers ---
|
|
280
|
+
function escapeHtml(s) {
|
|
281
|
+
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- Init ---
|
|
285
|
+
loadTier();
|
|
286
|
+
connectWS();
|
|
287
|
+
})();
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ClawKit | Agent Dashboard</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="style.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="crt-overlay"></div>
|
|
14
|
+
|
|
15
|
+
<div class="dashboard">
|
|
16
|
+
<!-- Header -->
|
|
17
|
+
<header class="header">
|
|
18
|
+
<div class="brand">
|
|
19
|
+
<span class="logo">⚡</span>
|
|
20
|
+
<div>
|
|
21
|
+
<div class="title">CLAWKIT <span class="dim">DASHBOARD</span></div>
|
|
22
|
+
<div class="subtitle" id="connectionStatus">Connecting...</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="status-bar">
|
|
26
|
+
<div class="status-item">
|
|
27
|
+
<span class="indicator" id="wsIndicator"></span>
|
|
28
|
+
<span id="wsLabel">OFFLINE</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="status-item" id="tierBadge">FREE</div>
|
|
31
|
+
<button class="icon-btn" id="settingsBtn" title="Settings">⚙</button>
|
|
32
|
+
</div>
|
|
33
|
+
</header>
|
|
34
|
+
|
|
35
|
+
<!-- Tabs -->
|
|
36
|
+
<nav class="tabs">
|
|
37
|
+
<button class="tab active" data-tab="dashboard">📊 Dashboard</button>
|
|
38
|
+
<button class="tab" data-tab="chat">💬 Chat</button>
|
|
39
|
+
<button class="tab" data-tab="tasks">📋 Tasks</button>
|
|
40
|
+
</nav>
|
|
41
|
+
|
|
42
|
+
<!-- Dashboard Tab -->
|
|
43
|
+
<div class="tab-content active" id="tab-dashboard">
|
|
44
|
+
<div class="grid-3">
|
|
45
|
+
<div class="panel">
|
|
46
|
+
<div class="panel-header"><span class="panel-title">AGENT STATUS</span></div>
|
|
47
|
+
<div class="panel-body" id="agentStatus">
|
|
48
|
+
<div class="stat-row"><span class="stat-label">Status</span><span class="stat-value" id="statAgentStatus">—</span></div>
|
|
49
|
+
<div class="stat-row"><span class="stat-label">Model</span><span class="stat-value" id="statModel">—</span></div>
|
|
50
|
+
<div class="stat-row"><span class="stat-label">Uptime</span><span class="stat-value" id="statUptime">—</span></div>
|
|
51
|
+
<div class="stat-row"><span class="stat-label">Channel</span><span class="stat-value" id="statChannel">—</span></div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="panel">
|
|
55
|
+
<div class="panel-header"><span class="panel-title">MEMORY HEALTH</span></div>
|
|
56
|
+
<div class="panel-body" id="memoryHealth">
|
|
57
|
+
<div class="stat-row"><span class="stat-label">Daily Notes</span><span class="stat-value" id="statDailyNotes">—</span></div>
|
|
58
|
+
<div class="stat-row"><span class="stat-label">Knowledge Files</span><span class="stat-value" id="statKnowledge">—</span></div>
|
|
59
|
+
<div class="stat-row"><span class="stat-label">MEMORY.md</span><span class="stat-value" id="statMemory">—</span></div>
|
|
60
|
+
<div class="stat-row"><span class="stat-label">LEARNINGS.md</span><span class="stat-value" id="statLearnings">—</span></div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="panel">
|
|
64
|
+
<div class="panel-header"><span class="panel-title">SKILLS</span></div>
|
|
65
|
+
<div class="panel-body" id="skillsPanel">
|
|
66
|
+
<div class="stat-row"><span class="stat-label">Installed</span><span class="stat-value" id="statSkillCount">—</span></div>
|
|
67
|
+
<div class="stat-row"><span class="stat-label">Tier</span><span class="stat-value" id="statTier">—</span></div>
|
|
68
|
+
<div class="stat-row"><span class="stat-label">Last Updated</span><span class="stat-value" id="statSkillsUpdated">—</span></div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="panel" style="margin-top:1rem">
|
|
73
|
+
<div class="panel-header"><span class="panel-title">ACTIVITY FEED</span></div>
|
|
74
|
+
<div class="panel-body feed-tape" id="activityFeed">
|
|
75
|
+
<div class="feed-empty">Waiting for events...</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Chat Tab -->
|
|
81
|
+
<div class="tab-content" id="tab-chat">
|
|
82
|
+
<div class="panel chat-panel">
|
|
83
|
+
<div class="panel-header"><span class="panel-title">CHAT</span></div>
|
|
84
|
+
<div class="chat-messages" id="chatMessages"></div>
|
|
85
|
+
<div class="chat-input-wrap" id="chatInputWrap">
|
|
86
|
+
<textarea id="chatInput" placeholder="Send a message..." rows="1" disabled></textarea>
|
|
87
|
+
<button id="chatSend" disabled>Send</button>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="pro-gate" id="chatProGate" style="display:none">
|
|
90
|
+
🔒 Chat requires <a href="https://clawkit.net/get-started" target="_blank">ClawKit Pro</a>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Tasks Tab -->
|
|
96
|
+
<div class="tab-content" id="tab-tasks">
|
|
97
|
+
<div class="panel tasks-panel">
|
|
98
|
+
<div class="panel-header">
|
|
99
|
+
<span class="panel-title">TASKS / QUEUE.md</span>
|
|
100
|
+
<button class="icon-btn" id="refreshTasks" title="Refresh">🔄</button>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="panel-body" id="tasksList">
|
|
103
|
+
<div class="feed-empty">Loading tasks...</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="task-input-wrap" id="taskInputWrap">
|
|
106
|
+
<input type="text" id="taskInput" placeholder="Add a task..." disabled>
|
|
107
|
+
<button id="taskAdd" disabled>Add</button>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="pro-gate" id="taskProGate" style="display:none">
|
|
110
|
+
🔒 Editing requires <a href="https://clawkit.net/get-started" target="_blank">ClawKit Pro</a>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Settings Modal -->
|
|
116
|
+
<div class="modal-overlay" id="settingsModal" style="display:none">
|
|
117
|
+
<div class="modal">
|
|
118
|
+
<div class="modal-header">
|
|
119
|
+
<span>⚙ Settings</span>
|
|
120
|
+
<button class="icon-btn" id="closeSettings">✕</button>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="modal-body">
|
|
123
|
+
<label class="form-label">WebSocket URL</label>
|
|
124
|
+
<input type="text" id="wsUrlInput" class="form-input" placeholder="ws://localhost:18789">
|
|
125
|
+
<div class="form-hint">Default: ws://localhost:18789</div>
|
|
126
|
+
<label class="form-label" style="margin-top:1rem">Pro Key</label>
|
|
127
|
+
<input type="text" id="proKeyInput" class="form-input" placeholder="clk_pro_...">
|
|
128
|
+
<div class="form-hint">Enter your Pro key to unlock all features</div>
|
|
129
|
+
<button class="btn-primary" id="saveSettings" style="margin-top:1.5rem">Save & Reconnect</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<script src="app.js"></script>
|
|
136
|
+
</body>
|
|
137
|
+
</html>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg-primary: #0a0a0f;
|
|
3
|
+
--bg-secondary: #111118;
|
|
4
|
+
--bg-tertiary: #1a1a24;
|
|
5
|
+
--bg-panel: #13131a;
|
|
6
|
+
--bg-hover: #1e1e2a;
|
|
7
|
+
--accent-green: #00ff88;
|
|
8
|
+
--accent-red: #ff3366;
|
|
9
|
+
--accent-blue: #00ccff;
|
|
10
|
+
--accent-yellow: #ffcc00;
|
|
11
|
+
--text-primary: #e8e8f0;
|
|
12
|
+
--text-secondary: #8888a0;
|
|
13
|
+
--text-dim: #555566;
|
|
14
|
+
--border-color: #2a2a3a;
|
|
15
|
+
--border-subtle: #1a1a2e;
|
|
16
|
+
--font-mono: 'JetBrains Mono','Fira Code',monospace;
|
|
17
|
+
--font-sans: 'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
21
|
+
html { font-size:14px; background:var(--bg-primary); }
|
|
22
|
+
body { font-family:var(--font-sans); background:var(--bg-primary); color:var(--text-primary); min-height:100vh; line-height:1.5; }
|
|
23
|
+
|
|
24
|
+
.crt-overlay { position:fixed; inset:0; background:linear-gradient(rgba(18,16,16,0) 50%,rgba(0,0,0,0.08) 50%); background-size:100% 4px; pointer-events:none; z-index:9999; opacity:.12; }
|
|
25
|
+
|
|
26
|
+
.dashboard { display:flex; flex-direction:column; min-height:100vh; padding:1rem; gap:.75rem; max-width:1400px; margin:0 auto; }
|
|
27
|
+
|
|
28
|
+
/* Header */
|
|
29
|
+
.header { display:flex; justify-content:space-between; align-items:center; padding:.75rem 1.25rem; background:var(--bg-secondary); border:1px solid var(--border-color); border-left:3px solid var(--accent-green); }
|
|
30
|
+
.brand { display:flex; align-items:center; gap:.75rem; }
|
|
31
|
+
.logo { font-size:1.4rem; color:var(--accent-green); text-shadow:0 0 10px rgba(0,255,136,.3); animation:pulse 2s ease-in-out infinite; }
|
|
32
|
+
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.7} }
|
|
33
|
+
.title { font-family:var(--font-mono); font-size:1rem; font-weight:600; letter-spacing:.05em; }
|
|
34
|
+
.subtitle { font-family:var(--font-mono); font-size:.7rem; color:var(--text-dim); }
|
|
35
|
+
.dim { color:var(--text-dim); }
|
|
36
|
+
.status-bar { display:flex; align-items:center; gap:1.25rem; font-family:var(--font-mono); font-size:.8rem; }
|
|
37
|
+
.status-item { display:flex; align-items:center; gap:.4rem; color:var(--text-secondary); }
|
|
38
|
+
.indicator { width:8px; height:8px; border-radius:50%; background:var(--text-dim); }
|
|
39
|
+
.indicator.live { background:var(--accent-green); box-shadow:0 0 10px rgba(0,255,136,.3); animation:blink 1s ease-in-out infinite; }
|
|
40
|
+
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:.3} }
|
|
41
|
+
.icon-btn { background:none; border:1px solid var(--border-subtle); color:var(--text-secondary); font-size:.9rem; padding:4px 8px; cursor:pointer; border-radius:4px; transition:all .2s; }
|
|
42
|
+
.icon-btn:hover { border-color:var(--accent-blue); color:var(--accent-blue); }
|
|
43
|
+
|
|
44
|
+
/* Tabs */
|
|
45
|
+
.tabs { display:flex; gap:0; border-bottom:1px solid var(--border-color); }
|
|
46
|
+
.tab { background:none; border:none; color:var(--text-dim); font-family:var(--font-mono); font-size:.8rem; padding:.6rem 1.25rem; cursor:pointer; border-bottom:2px solid transparent; transition:all .2s; }
|
|
47
|
+
.tab:hover { color:var(--text-secondary); }
|
|
48
|
+
.tab.active { color:var(--accent-green); border-bottom-color:var(--accent-green); }
|
|
49
|
+
|
|
50
|
+
/* Tab content */
|
|
51
|
+
.tab-content { display:none; flex:1; min-height:0; }
|
|
52
|
+
.tab-content.active { display:flex; flex-direction:column; }
|
|
53
|
+
|
|
54
|
+
/* Grid */
|
|
55
|
+
.grid-3 { display:grid; grid-template-columns:repeat(3,1fr); gap:.75rem; }
|
|
56
|
+
|
|
57
|
+
/* Panels */
|
|
58
|
+
.panel { background:var(--bg-panel); border:1px solid var(--border-color); display:flex; flex-direction:column; overflow:hidden; }
|
|
59
|
+
.panel-header { display:flex; justify-content:space-between; align-items:center; padding:.5rem 1rem; background:var(--bg-secondary); border-bottom:1px solid var(--border-color); font-family:var(--font-mono); font-size:.7rem; font-weight:600; letter-spacing:.1em; }
|
|
60
|
+
.panel-title { color:var(--text-secondary); }
|
|
61
|
+
.panel-body { padding:.75rem 1rem; flex:1; overflow-y:auto; }
|
|
62
|
+
|
|
63
|
+
/* Stats */
|
|
64
|
+
.stat-row { display:flex; justify-content:space-between; align-items:center; padding:.4rem 0; border-bottom:1px solid var(--border-subtle); }
|
|
65
|
+
.stat-row:last-child { border-bottom:none; }
|
|
66
|
+
.stat-label { color:var(--text-dim); font-size:.8rem; }
|
|
67
|
+
.stat-value { font-family:var(--font-mono); font-size:.8rem; color:var(--accent-green); }
|
|
68
|
+
|
|
69
|
+
/* Feed */
|
|
70
|
+
.feed-tape { max-height:300px; }
|
|
71
|
+
.feed-empty { color:var(--text-dim); font-size:.8rem; text-align:center; padding:2rem; }
|
|
72
|
+
.feed-item { display:flex; gap:.75rem; padding:.4rem .5rem; font-family:var(--font-mono); font-size:.75rem; border-left:2px solid var(--border-subtle); margin-bottom:2px; }
|
|
73
|
+
.feed-item:hover { background:var(--bg-hover); }
|
|
74
|
+
.feed-time { color:var(--text-dim); min-width:55px; }
|
|
75
|
+
.feed-msg { color:var(--text-primary); flex:1; }
|
|
76
|
+
|
|
77
|
+
/* Chat */
|
|
78
|
+
.chat-panel { flex:1; }
|
|
79
|
+
.chat-messages { flex:1; overflow-y:auto; padding:.75rem; display:flex; flex-direction:column; gap:.5rem; min-height:300px; }
|
|
80
|
+
.chat-msg { padding:.5rem .75rem; border-radius:6px; max-width:80%; font-size:.85rem; line-height:1.4; }
|
|
81
|
+
.chat-msg.user { background:rgba(0,204,255,.1); border:1px solid rgba(0,204,255,.2); align-self:flex-end; }
|
|
82
|
+
.chat-msg.agent { background:var(--bg-tertiary); border:1px solid var(--border-subtle); align-self:flex-start; }
|
|
83
|
+
.chat-msg .msg-meta { font-family:var(--font-mono); font-size:.65rem; color:var(--text-dim); margin-bottom:.25rem; }
|
|
84
|
+
.chat-input-wrap { display:flex; gap:.5rem; padding:.5rem .75rem; border-top:1px solid var(--border-color); }
|
|
85
|
+
.chat-input-wrap textarea { flex:1; background:var(--bg-secondary); border:1px solid var(--border-subtle); color:var(--text-primary); font-family:var(--font-sans); font-size:.85rem; padding:.5rem; resize:none; border-radius:4px; }
|
|
86
|
+
.chat-input-wrap textarea:focus { outline:none; border-color:var(--accent-blue); }
|
|
87
|
+
.chat-input-wrap button, .task-input-wrap button { background:var(--accent-green); color:#000; border:none; padding:.5rem 1rem; font-family:var(--font-mono); font-size:.75rem; font-weight:600; cursor:pointer; border-radius:4px; }
|
|
88
|
+
.chat-input-wrap button:disabled, .task-input-wrap button:disabled { opacity:.3; cursor:not-allowed; }
|
|
89
|
+
|
|
90
|
+
/* Tasks */
|
|
91
|
+
.tasks-panel { flex:1; }
|
|
92
|
+
.task-item { display:flex; align-items:center; gap:.5rem; padding:.4rem .5rem; font-size:.8rem; border-bottom:1px solid var(--border-subtle); }
|
|
93
|
+
.task-item .task-check { color:var(--accent-green); }
|
|
94
|
+
.task-item .task-text { flex:1; }
|
|
95
|
+
.task-item.done .task-text { text-decoration:line-through; color:var(--text-dim); }
|
|
96
|
+
.task-input-wrap { display:flex; gap:.5rem; padding:.5rem .75rem; border-top:1px solid var(--border-color); }
|
|
97
|
+
.task-input-wrap input { flex:1; background:var(--bg-secondary); border:1px solid var(--border-subtle); color:var(--text-primary); font-family:var(--font-sans); font-size:.85rem; padding:.5rem; border-radius:4px; }
|
|
98
|
+
.task-input-wrap input:focus { outline:none; border-color:var(--accent-blue); }
|
|
99
|
+
|
|
100
|
+
/* Pro gate */
|
|
101
|
+
.pro-gate { text-align:center; padding:.75rem; font-size:.8rem; color:var(--text-dim); border-top:1px solid var(--border-color); }
|
|
102
|
+
.pro-gate a { color:var(--accent-blue); }
|
|
103
|
+
|
|
104
|
+
/* Modal */
|
|
105
|
+
.modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.7); display:flex; align-items:center; justify-content:center; z-index:10000; }
|
|
106
|
+
.modal { background:var(--bg-secondary); border:1px solid var(--border-color); width:400px; max-width:90vw; border-radius:8px; }
|
|
107
|
+
.modal-header { display:flex; justify-content:space-between; align-items:center; padding:.75rem 1rem; border-bottom:1px solid var(--border-color); font-family:var(--font-mono); font-size:.85rem; }
|
|
108
|
+
.modal-body { padding:1.25rem; }
|
|
109
|
+
.form-label { display:block; font-family:var(--font-mono); font-size:.75rem; color:var(--text-secondary); margin-bottom:.35rem; letter-spacing:.05em; }
|
|
110
|
+
.form-input { width:100%; background:var(--bg-primary); border:1px solid var(--border-subtle); color:var(--text-primary); font-family:var(--font-mono); font-size:.8rem; padding:.5rem; border-radius:4px; }
|
|
111
|
+
.form-input:focus { outline:none; border-color:var(--accent-blue); }
|
|
112
|
+
.form-hint { font-size:.7rem; color:var(--text-dim); margin-top:.25rem; }
|
|
113
|
+
.btn-primary { background:var(--accent-green); color:#000; border:none; padding:.5rem 1.25rem; font-family:var(--font-mono); font-size:.8rem; font-weight:600; cursor:pointer; border-radius:4px; width:100%; }
|
|
114
|
+
.btn-primary:hover { opacity:.9; }
|
|
115
|
+
|
|
116
|
+
/* Responsive */
|
|
117
|
+
@media (max-width:768px) {
|
|
118
|
+
.grid-3 { grid-template-columns:1fr; }
|
|
119
|
+
.header { flex-direction:column; gap:.5rem; }
|
|
120
|
+
.status-bar { justify-content:center; }
|
|
121
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clamper-ai",
|
|
3
|
+
"version": "1.1.6",
|
|
4
|
+
"description": "Transform your OpenClaw agent into a production-ready AI assistant with memory, skills, and a dashboard",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clamper": "./bin/clamper.mjs"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"openclaw",
|
|
11
|
+
"ai",
|
|
12
|
+
"agent",
|
|
13
|
+
"skills",
|
|
14
|
+
"memory",
|
|
15
|
+
"dashboard",
|
|
16
|
+
"clamper"
|
|
17
|
+
],
|
|
18
|
+
"author": "Clamper",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {}
|
|
24
|
+
}
|