clawck 0.1.0

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +322 -0
  3. package/dist/cli/index.d.ts +14 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +233 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/core/clawck.d.ts +48 -0
  8. package/dist/core/clawck.d.ts.map +1 -0
  9. package/dist/core/clawck.js +119 -0
  10. package/dist/core/clawck.js.map +1 -0
  11. package/dist/core/database.d.ts +55 -0
  12. package/dist/core/database.d.ts.map +1 -0
  13. package/dist/core/database.js +329 -0
  14. package/dist/core/database.js.map +1 -0
  15. package/dist/core/index.d.ts +4 -0
  16. package/dist/core/index.d.ts.map +1 -0
  17. package/dist/core/index.js +23 -0
  18. package/dist/core/index.js.map +1 -0
  19. package/dist/core/sync.d.ts +17 -0
  20. package/dist/core/sync.d.ts.map +1 -0
  21. package/dist/core/sync.js +91 -0
  22. package/dist/core/sync.js.map +1 -0
  23. package/dist/core/types.d.ts +186 -0
  24. package/dist/core/types.d.ts.map +1 -0
  25. package/dist/core/types.js +34 -0
  26. package/dist/core/types.js.map +1 -0
  27. package/dist/dashboard/index.d.ts +6 -0
  28. package/dist/dashboard/index.d.ts.map +1 -0
  29. package/dist/dashboard/index.js +632 -0
  30. package/dist/dashboard/index.js.map +1 -0
  31. package/dist/index.d.ts +30 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +53 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/server/api.d.ts +15 -0
  36. package/dist/server/api.d.ts.map +1 -0
  37. package/dist/server/api.js +193 -0
  38. package/dist/server/api.js.map +1 -0
  39. package/dist/server/index.d.ts +3 -0
  40. package/dist/server/index.d.ts.map +1 -0
  41. package/dist/server/index.js +9 -0
  42. package/dist/server/index.js.map +1 -0
  43. package/dist/server/mcp.d.ts +8 -0
  44. package/dist/server/mcp.d.ts.map +1 -0
  45. package/dist/server/mcp.js +277 -0
  46. package/dist/server/mcp.js.map +1 -0
  47. package/package.json +66 -0
@@ -0,0 +1,632 @@
1
+ "use strict";
2
+ /**
3
+ * โฑ๏ธ๐Ÿฆ€ Clawck โ€” Dashboard
4
+ * Self-contained HTML dashboard served by the Clawck server.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getDashboardHTML = getDashboardHTML;
8
+ function getDashboardHTML(port) {
9
+ return `<!DOCTYPE html>
10
+ <html lang="en">
11
+ <head>
12
+ <meta charset="UTF-8">
13
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
14
+ <title>Clawck โ€” AI Agent Time Tracker</title>
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
17
+ <style>
18
+ :root {
19
+ --bg-primary: #0a0b0f;
20
+ --bg-secondary: #12141c;
21
+ --bg-card: #181a24;
22
+ --bg-hover: #1e2030;
23
+ --border: #2a2d3e;
24
+ --text-primary: #e8eaf0;
25
+ --text-secondary: #8b8fa3;
26
+ --text-muted: #5c5f73;
27
+ --accent: #ff6b35;
28
+ --accent-glow: rgba(255, 107, 53, 0.15);
29
+ --green: #34d399;
30
+ --green-dim: rgba(52, 211, 153, 0.15);
31
+ --blue: #60a5fa;
32
+ --blue-dim: rgba(96, 165, 250, 0.15);
33
+ --red: #f87171;
34
+ --red-dim: rgba(248, 113, 113, 0.15);
35
+ --yellow: #fbbf24;
36
+ --yellow-dim: rgba(251, 191, 36, 0.15);
37
+ --purple: #a78bfa;
38
+ --radius: 8px;
39
+ --radius-lg: 12px;
40
+ }
41
+ * { margin: 0; padding: 0; box-sizing: border-box; }
42
+ body {
43
+ font-family: 'DM Sans', -apple-system, sans-serif;
44
+ background: var(--bg-primary);
45
+ color: var(--text-primary);
46
+ min-height: 100vh;
47
+ line-height: 1.5;
48
+ }
49
+
50
+ /* โ”€โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
51
+ .header {
52
+ background: var(--bg-secondary);
53
+ border-bottom: 1px solid var(--border);
54
+ padding: 16px 32px;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: space-between;
58
+ position: sticky;
59
+ top: 0;
60
+ z-index: 100;
61
+ }
62
+ .logo {
63
+ font-family: 'JetBrains Mono', monospace;
64
+ font-size: 20px;
65
+ font-weight: 700;
66
+ color: var(--accent);
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 10px;
70
+ }
71
+ .logo-icon { font-size: 24px; }
72
+ .logo-sub {
73
+ font-size: 11px;
74
+ font-weight: 400;
75
+ color: var(--text-muted);
76
+ font-family: 'DM Sans', sans-serif;
77
+ margin-left: 4px;
78
+ }
79
+ .header-right { display: flex; align-items: center; gap: 16px; }
80
+ .period-select {
81
+ background: var(--bg-card);
82
+ border: 1px solid var(--border);
83
+ color: var(--text-primary);
84
+ padding: 8px 14px;
85
+ border-radius: var(--radius);
86
+ font-family: 'DM Sans', sans-serif;
87
+ font-size: 13px;
88
+ cursor: pointer;
89
+ }
90
+ .refresh-btn {
91
+ background: var(--accent);
92
+ color: #fff;
93
+ border: none;
94
+ padding: 8px 18px;
95
+ border-radius: var(--radius);
96
+ font-family: 'DM Sans', sans-serif;
97
+ font-size: 13px;
98
+ font-weight: 600;
99
+ cursor: pointer;
100
+ transition: opacity 0.2s;
101
+ }
102
+ .refresh-btn:hover { opacity: 0.85; }
103
+
104
+ /* โ”€โ”€โ”€ Layout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
105
+ .main { padding: 28px 32px; max-width: 1400px; margin: 0 auto; }
106
+
107
+ /* โ”€โ”€โ”€ Stat Cards โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
108
+ .stats-grid {
109
+ display: grid;
110
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
111
+ gap: 16px;
112
+ margin-bottom: 32px;
113
+ }
114
+ .stat-card {
115
+ background: var(--bg-card);
116
+ border: 1px solid var(--border);
117
+ border-radius: var(--radius-lg);
118
+ padding: 20px 22px;
119
+ }
120
+ .stat-label {
121
+ font-size: 12px;
122
+ font-weight: 500;
123
+ color: var(--text-muted);
124
+ text-transform: uppercase;
125
+ letter-spacing: 0.5px;
126
+ margin-bottom: 6px;
127
+ }
128
+ .stat-value {
129
+ font-family: 'JetBrains Mono', monospace;
130
+ font-size: 28px;
131
+ font-weight: 700;
132
+ line-height: 1.1;
133
+ }
134
+ .stat-sub {
135
+ font-size: 12px;
136
+ color: var(--text-secondary);
137
+ margin-top: 4px;
138
+ }
139
+ .stat-accent { color: var(--accent); }
140
+ .stat-green { color: var(--green); }
141
+ .stat-blue { color: var(--blue); }
142
+
143
+ /* โ”€โ”€โ”€ Savings Banner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
144
+ .savings-banner {
145
+ background: linear-gradient(135deg, rgba(52, 211, 153, 0.08) 0%, rgba(96, 165, 250, 0.06) 100%);
146
+ border: 1px solid rgba(52, 211, 153, 0.2);
147
+ border-radius: var(--radius-lg);
148
+ padding: 24px 28px;
149
+ margin-bottom: 32px;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: space-between;
153
+ flex-wrap: wrap;
154
+ gap: 16px;
155
+ }
156
+ .savings-left h3 {
157
+ font-size: 14px;
158
+ color: var(--green);
159
+ font-weight: 600;
160
+ margin-bottom: 4px;
161
+ }
162
+ .savings-left .savings-amount {
163
+ font-family: 'JetBrains Mono', monospace;
164
+ font-size: 36px;
165
+ font-weight: 700;
166
+ color: var(--green);
167
+ }
168
+ .savings-left .savings-detail {
169
+ font-size: 13px;
170
+ color: var(--text-secondary);
171
+ margin-top: 4px;
172
+ }
173
+ .savings-right {
174
+ text-align: right;
175
+ }
176
+ .savings-right .equiv-hours {
177
+ font-family: 'JetBrains Mono', monospace;
178
+ font-size: 24px;
179
+ font-weight: 700;
180
+ color: var(--blue);
181
+ }
182
+ .savings-right .equiv-label {
183
+ font-size: 12px;
184
+ color: var(--text-secondary);
185
+ }
186
+
187
+ /* โ”€โ”€โ”€ Sections โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
188
+ .section { margin-bottom: 32px; }
189
+ .section-header {
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: space-between;
193
+ margin-bottom: 16px;
194
+ }
195
+ .section-title {
196
+ font-size: 16px;
197
+ font-weight: 600;
198
+ color: var(--text-primary);
199
+ }
200
+
201
+ /* โ”€โ”€โ”€ Tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
202
+ .tabs {
203
+ display: flex;
204
+ gap: 2px;
205
+ background: var(--bg-secondary);
206
+ border-radius: var(--radius);
207
+ padding: 3px;
208
+ margin-bottom: 20px;
209
+ border: 1px solid var(--border);
210
+ width: fit-content;
211
+ }
212
+ .tab {
213
+ padding: 8px 18px;
214
+ border-radius: 6px;
215
+ font-size: 13px;
216
+ font-weight: 500;
217
+ cursor: pointer;
218
+ color: var(--text-secondary);
219
+ background: none;
220
+ border: none;
221
+ transition: all 0.2s;
222
+ font-family: 'DM Sans', sans-serif;
223
+ }
224
+ .tab:hover { color: var(--text-primary); }
225
+ .tab.active {
226
+ background: var(--bg-card);
227
+ color: var(--accent);
228
+ box-shadow: 0 1px 3px rgba(0,0,0,0.2);
229
+ }
230
+
231
+ /* โ”€โ”€โ”€ Table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
232
+ .table-wrap {
233
+ background: var(--bg-card);
234
+ border: 1px solid var(--border);
235
+ border-radius: var(--radius-lg);
236
+ overflow: hidden;
237
+ }
238
+ table { width: 100%; border-collapse: collapse; }
239
+ th {
240
+ text-align: left;
241
+ padding: 12px 16px;
242
+ font-size: 11px;
243
+ font-weight: 600;
244
+ text-transform: uppercase;
245
+ letter-spacing: 0.5px;
246
+ color: var(--text-muted);
247
+ background: var(--bg-secondary);
248
+ border-bottom: 1px solid var(--border);
249
+ }
250
+ td {
251
+ padding: 14px 16px;
252
+ font-size: 13px;
253
+ border-bottom: 1px solid var(--border);
254
+ color: var(--text-secondary);
255
+ }
256
+ tr:last-child td { border-bottom: none; }
257
+ tr:hover td { background: var(--bg-hover); }
258
+ .cell-mono {
259
+ font-family: 'JetBrains Mono', monospace;
260
+ font-size: 12px;
261
+ }
262
+ .cell-task {
263
+ color: var(--text-primary);
264
+ font-weight: 500;
265
+ max-width: 300px;
266
+ overflow: hidden;
267
+ text-overflow: ellipsis;
268
+ white-space: nowrap;
269
+ }
270
+
271
+ /* โ”€โ”€โ”€ Badges โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
272
+ .badge {
273
+ display: inline-block;
274
+ padding: 3px 10px;
275
+ border-radius: 20px;
276
+ font-size: 11px;
277
+ font-weight: 600;
278
+ }
279
+ .badge-running { background: var(--accent-glow); color: var(--accent); }
280
+ .badge-completed { background: var(--green-dim); color: var(--green); }
281
+ .badge-failed { background: var(--red-dim); color: var(--red); }
282
+ .badge-paused { background: var(--yellow-dim); color: var(--yellow); }
283
+ .badge-cat {
284
+ background: var(--blue-dim);
285
+ color: var(--blue);
286
+ font-size: 10px;
287
+ padding: 2px 8px;
288
+ }
289
+
290
+ /* โ”€โ”€โ”€ Breakdown Bars โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
291
+ .breakdown-list { display: flex; flex-direction: column; gap: 10px; }
292
+ .breakdown-row {
293
+ display: flex;
294
+ align-items: center;
295
+ gap: 12px;
296
+ }
297
+ .breakdown-label {
298
+ font-size: 13px;
299
+ font-weight: 500;
300
+ width: 140px;
301
+ flex-shrink: 0;
302
+ overflow: hidden;
303
+ text-overflow: ellipsis;
304
+ white-space: nowrap;
305
+ }
306
+ .breakdown-bar-wrap {
307
+ flex: 1;
308
+ height: 24px;
309
+ background: var(--bg-secondary);
310
+ border-radius: 4px;
311
+ overflow: hidden;
312
+ }
313
+ .breakdown-bar {
314
+ height: 100%;
315
+ background: var(--accent);
316
+ border-radius: 4px;
317
+ transition: width 0.6s ease;
318
+ min-width: 2px;
319
+ }
320
+ .breakdown-value {
321
+ font-family: 'JetBrains Mono', monospace;
322
+ font-size: 12px;
323
+ color: var(--text-secondary);
324
+ width: 80px;
325
+ text-align: right;
326
+ flex-shrink: 0;
327
+ }
328
+
329
+ /* โ”€โ”€โ”€ Empty State โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
330
+ .empty {
331
+ text-align: center;
332
+ padding: 60px 20px;
333
+ color: var(--text-muted);
334
+ }
335
+ .empty-icon { font-size: 48px; margin-bottom: 16px; }
336
+ .empty-title { font-size: 18px; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px; }
337
+ .empty-sub { font-size: 14px; }
338
+ .empty code {
339
+ background: var(--bg-secondary);
340
+ padding: 2px 8px;
341
+ border-radius: 4px;
342
+ font-family: 'JetBrains Mono', monospace;
343
+ font-size: 12px;
344
+ }
345
+
346
+ /* โ”€โ”€โ”€ Two Column โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */
347
+ .two-col {
348
+ display: grid;
349
+ grid-template-columns: 1fr 1fr;
350
+ gap: 20px;
351
+ }
352
+ @media (max-width: 900px) {
353
+ .two-col { grid-template-columns: 1fr; }
354
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
355
+ .savings-banner { flex-direction: column; text-align: left; }
356
+ .savings-right { text-align: left; }
357
+ }
358
+
359
+ .loading {
360
+ display: flex;
361
+ align-items: center;
362
+ justify-content: center;
363
+ padding: 60px;
364
+ color: var(--text-muted);
365
+ font-size: 14px;
366
+ }
367
+ .pulse { animation: pulse 1.5s infinite; }
368
+ @keyframes pulse {
369
+ 0%, 100% { opacity: 1; }
370
+ 50% { opacity: 0.4; }
371
+ }
372
+ </style>
373
+ </head>
374
+ <body>
375
+ <div class="header">
376
+ <div class="logo">
377
+ <span class="logo-icon">๐Ÿฆ€</span>
378
+ <span>Clawck</span>
379
+ <span class="logo-sub">Agent Time Tracker</span>
380
+ </div>
381
+ <div class="header-right">
382
+ <select class="period-select" id="periodSelect">
383
+ <option value="1">Today</option>
384
+ <option value="7" selected>Last 7 days</option>
385
+ <option value="14">Last 14 days</option>
386
+ <option value="30">Last 30 days</option>
387
+ <option value="90">Last 90 days</option>
388
+ </select>
389
+ <button class="refresh-btn" onclick="loadData()">Refresh</button>
390
+ </div>
391
+ </div>
392
+
393
+ <div class="main" id="app">
394
+ <div class="loading"><span class="pulse">โฑ๏ธ Loading Clawck data...</span></div>
395
+ </div>
396
+
397
+ <script>
398
+ const API = 'http://localhost:${port}/api';
399
+ let data = null;
400
+
401
+ async function loadData() {
402
+ const days = parseInt(document.getElementById('periodSelect').value);
403
+ const to = new Date().toISOString();
404
+ const from = new Date(Date.now() - days * 86400000).toISOString();
405
+
406
+ try {
407
+ const [tsRes, runRes, statsRes] = await Promise.all([
408
+ fetch(API + '/timesheet?from=' + from + '&to=' + to).then(r => r.json()),
409
+ fetch(API + '/running').then(r => r.json()),
410
+ fetch(API + '/stats').then(r => r.json()),
411
+ ]);
412
+ data = { timesheet: tsRes, running: runRes, stats: statsRes };
413
+ render();
414
+ } catch (e) {
415
+ document.getElementById('app').innerHTML = \`
416
+ <div class="empty">
417
+ <div class="empty-icon">๐Ÿฆ€</div>
418
+ <div class="empty-title">Clawck is running but no data yet</div>
419
+ <div class="empty-sub">Start tracking: <code>clawck_start_task</code> via MCP or <code>POST /api/start</code></div>
420
+ </div>\`;
421
+ }
422
+ }
423
+
424
+ function render() {
425
+ if (!data) return;
426
+ const ts = data.timesheet;
427
+ const running = data.running;
428
+ const stats = data.stats;
429
+
430
+ const app = document.getElementById('app');
431
+ app.innerHTML = '';
432
+
433
+ // โ”€โ”€โ”€ Stats Cards โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
434
+ const statsHTML = \`
435
+ <div class="stats-grid">
436
+ <div class="stat-card">
437
+ <div class="stat-label">Agent Hours</div>
438
+ <div class="stat-value stat-accent">\${ts.total_agent_hours.toFixed(1)}</div>
439
+ <div class="stat-sub">\${ts.total_entries} entries</div>
440
+ </div>
441
+ <div class="stat-card">
442
+ <div class="stat-label">Human Equiv Hours</div>
443
+ <div class="stat-value stat-blue">\${ts.total_human_equiv_hours.toFixed(1)}</div>
444
+ <div class="stat-sub">\${(ts.total_human_equiv_hours / (ts.total_agent_hours || 1)).toFixed(1)}x multiplier avg</div>
445
+ </div>
446
+ <div class="stat-card">
447
+ <div class="stat-label">Agent Cost</div>
448
+ <div class="stat-value">$\${ts.total_cost_usd.toFixed(2)}</div>
449
+ <div class="stat-sub">\${ts.total_tokens.toLocaleString()} tokens</div>
450
+ </div>
451
+ <div class="stat-card">
452
+ <div class="stat-label">Active Now</div>
453
+ <div class="stat-value" style="color: \${running.length > 0 ? 'var(--accent)' : 'var(--text-muted)'}">\${running.length}</div>
454
+ <div class="stat-sub">\${stats.agents} agents total</div>
455
+ </div>
456
+ </div>\`;
457
+
458
+ // โ”€โ”€โ”€ Savings Banner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
459
+ const savingsHTML = ts.total_savings_usd > 0 ? \`
460
+ <div class="savings-banner">
461
+ <div class="savings-left">
462
+ <h3>๐Ÿ’š Estimated Value Delivered</h3>
463
+ <div class="savings-amount">$\${ts.total_savings_usd.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 0})}</div>
464
+ <div class="savings-detail">Based on human-equivalent hours ร— avg hourly rates</div>
465
+ </div>
466
+ <div class="savings-right">
467
+ <div class="equiv-hours">\${ts.total_human_equiv_hours.toFixed(1)} hrs</div>
468
+ <div class="equiv-label">of human work completed by agents</div>
469
+ </div>
470
+ </div>\` : '';
471
+
472
+ // โ”€โ”€โ”€ Tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
473
+ const tabsHTML = \`
474
+ <div class="tabs">
475
+ <button class="tab active" onclick="showTab('entries', this)">Time Entries</button>
476
+ <button class="tab" onclick="showTab('projects', this)">By Project</button>
477
+ <button class="tab" onclick="showTab('agents', this)">By Agent</button>
478
+ <button class="tab" onclick="showTab('categories', this)">By Category</button>
479
+ </div>\`;
480
+
481
+ // โ”€โ”€โ”€ Entries Table โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
482
+ const entriesRows = ts.entries.slice(0, 100).map(e => \`
483
+ <tr>
484
+ <td class="cell-mono">\${e.date}</td>
485
+ <td class="cell-task">\${esc(e.task)}</td>
486
+ <td>\${esc(e.project)}</td>
487
+ <td>\${esc(e.client)}</td>
488
+ <td>\${esc(e.agent)}</td>
489
+ <td><span class="badge badge-cat">\${e.category}</span></td>
490
+ <td class="cell-mono">\${formatDuration(e.duration_minutes)}</td>
491
+ <td class="cell-mono">\${e.human_equiv_hours.toFixed(1)}h</td>
492
+ <td class="cell-mono">$\${e.cost_usd.toFixed(4)}</td>
493
+ <td><span class="badge badge-\${e.status}">\${e.status}</span></td>
494
+ </tr>\`).join('');
495
+
496
+ const entriesHTML = ts.entries.length > 0 ? \`
497
+ <div class="table-wrap">
498
+ <table>
499
+ <thead>
500
+ <tr>
501
+ <th>Date</th><th>Task</th><th>Project</th><th>Client</th><th>Agent</th><th>Category</th><th>Duration</th><th>Human Equiv</th><th>Cost</th><th>Status</th>
502
+ </tr>
503
+ </thead>
504
+ <tbody>\${entriesRows}</tbody>
505
+ </table>
506
+ </div>\` : \`
507
+ <div class="empty">
508
+ <div class="empty-icon">๐Ÿ“‹</div>
509
+ <div class="empty-title">No entries yet</div>
510
+ <div class="empty-sub">Agents will appear here once they start clocking in</div>
511
+ </div>\`;
512
+
513
+ // โ”€โ”€โ”€ By Project โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
514
+ const maxProjHours = Math.max(...ts.by_project.map(p => p.agent_hours), 0.1);
515
+ const projectsHTML = \`
516
+ <div class="table-wrap" style="padding: 20px;">
517
+ <div class="breakdown-list">
518
+ \${ts.by_project.map(p => \`
519
+ <div class="breakdown-row">
520
+ <div class="breakdown-label">\${esc(p.project)}</div>
521
+ <div class="breakdown-bar-wrap">
522
+ <div class="breakdown-bar" style="width: \${(p.agent_hours / maxProjHours * 100).toFixed(1)}%"></div>
523
+ </div>
524
+ <div class="breakdown-value">\${p.agent_hours.toFixed(1)}h โ†’ \${p.human_equiv_hours.toFixed(1)}h</div>
525
+ </div>
526
+ \`).join('') || '<div class="empty-sub">No project data</div>'}
527
+ </div>
528
+ </div>\`;
529
+
530
+ // โ”€โ”€โ”€ By Agent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
531
+ const maxAgentHours = Math.max(...ts.by_agent.map(a => a.agent_hours), 0.1);
532
+ const agentsHTML = \`
533
+ <div class="table-wrap" style="padding: 20px;">
534
+ <div class="breakdown-list">
535
+ \${ts.by_agent.map(a => \`
536
+ <div class="breakdown-row">
537
+ <div class="breakdown-label">\${esc(a.agent)}</div>
538
+ <div class="breakdown-bar-wrap">
539
+ <div class="breakdown-bar" style="width: \${(a.agent_hours / maxAgentHours * 100).toFixed(1)}%; background: var(--blue)"></div>
540
+ </div>
541
+ <div class="breakdown-value">\${a.agent_hours.toFixed(1)}h ยท \${a.success_rate}%</div>
542
+ </div>
543
+ \`).join('') || '<div class="empty-sub">No agent data</div>'}
544
+ </div>
545
+ </div>\`;
546
+
547
+ // โ”€โ”€โ”€ By Category โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
548
+ const maxCatHours = Math.max(...ts.by_category.map(c => c.agent_hours), 0.1);
549
+ const catColors = { research: '--purple', content: '--green', code: '--blue', data_entry: '--yellow', design: '--accent', communication: '--text-primary', analysis: '--purple', testing: '--red', planning: '--blue', other: '--text-muted' };
550
+ const catsHTML = \`
551
+ <div class="table-wrap" style="padding: 20px;">
552
+ <div class="breakdown-list">
553
+ \${ts.by_category.map(c => \`
554
+ <div class="breakdown-row">
555
+ <div class="breakdown-label">\${c.category}</div>
556
+ <div class="breakdown-bar-wrap">
557
+ <div class="breakdown-bar" style="width: \${(c.agent_hours / maxCatHours * 100).toFixed(1)}%; background: var(\${catColors[c.category] || '--accent'})"></div>
558
+ </div>
559
+ <div class="breakdown-value">\${c.agent_hours.toFixed(1)}h ยท $\${c.savings_usd.toFixed(0)} saved</div>
560
+ </div>
561
+ \`).join('') || '<div class="empty-sub">No category data</div>'}
562
+ </div>
563
+ </div>\`;
564
+
565
+ // โ”€โ”€โ”€ Running Now โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
566
+ const runningHTML = running.length > 0 ? \`
567
+ <div class="section">
568
+ <div class="section-header">
569
+ <div class="section-title">โฑ๏ธ Running Now</div>
570
+ </div>
571
+ <div class="table-wrap">
572
+ <table>
573
+ <thead><tr><th>Agent</th><th>Task</th><th>Project</th><th>Client</th><th>Running For</th></tr></thead>
574
+ <tbody>
575
+ \${running.map(e => {
576
+ const mins = Math.round((Date.now() - new Date(e.start).getTime()) / 60000);
577
+ return \`<tr>
578
+ <td>\${esc(e.agent)}</td>
579
+ <td class="cell-task">\${esc(e.task)}</td>
580
+ <td>\${esc(e.project)}</td>
581
+ <td>\${esc(e.client)}</td>
582
+ <td class="cell-mono" style="color:var(--accent)">\${formatDuration(mins)}</td>
583
+ </tr>\`;
584
+ }).join('')}
585
+ </tbody>
586
+ </table>
587
+ </div>
588
+ </div>\` : '';
589
+
590
+ // โ”€โ”€โ”€ Assemble โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
591
+ app.innerHTML = statsHTML + savingsHTML + runningHTML + \`
592
+ <div class="section">
593
+ \${tabsHTML}
594
+ <div id="tab-entries">\${entriesHTML}</div>
595
+ <div id="tab-projects" style="display:none">\${projectsHTML}</div>
596
+ <div id="tab-agents" style="display:none">\${agentsHTML}</div>
597
+ <div id="tab-categories" style="display:none">\${catsHTML}</div>
598
+ </div>\`;
599
+ }
600
+
601
+ function showTab(name, btn) {
602
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
603
+ btn.classList.add('active');
604
+ ['entries','projects','agents','categories'].forEach(t => {
605
+ const el = document.getElementById('tab-' + t);
606
+ if (el) el.style.display = t === name ? 'block' : 'none';
607
+ });
608
+ }
609
+
610
+ function formatDuration(mins) {
611
+ if (mins < 1) return '<1m';
612
+ if (mins < 60) return Math.round(mins) + 'm';
613
+ const h = Math.floor(mins / 60);
614
+ const m = Math.round(mins % 60);
615
+ return h + 'h ' + (m > 0 ? m + 'm' : '');
616
+ }
617
+
618
+ function esc(s) {
619
+ if (!s) return '';
620
+ const d = document.createElement('div');
621
+ d.textContent = s;
622
+ return d.innerHTML;
623
+ }
624
+
625
+ document.getElementById('periodSelect').addEventListener('change', loadData);
626
+ loadData();
627
+ setInterval(loadData, 30000); // Auto-refresh every 30s
628
+ </script>
629
+ </body>
630
+ </html>`;
631
+ }
632
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,4CA+mBC;AA/mBD,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAqYuB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwO5B,CAAC;AACT,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * โฑ๏ธ๐Ÿฆ€ Clawck
3
+ * Time tracking for AI agents. Toggl for the agentic era.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { Clawck } from 'clawck';
8
+ *
9
+ * const clawck = new Clawck({ default_client: 'acme-corp' });
10
+ *
11
+ * // Start tracking
12
+ * const entry = clawck.start({ task: 'Research grants', project: 'grant-research', category: 'research' });
13
+ *
14
+ * // ... agent does work ...
15
+ *
16
+ * // Stop tracking
17
+ * clawck.stop({ id: entry.id, status: 'completed', summary: 'Found 12 matching grants' });
18
+ *
19
+ * // Get timesheet
20
+ * const report = clawck.timesheet('2026-03-01', '2026-03-07');
21
+ * console.log(`Saved ${report.total_savings_usd} in human-equivalent work`);
22
+ * ```
23
+ */
24
+ export { Clawck } from './core/clawck';
25
+ export { ClawckDB } from './core/database';
26
+ export { SyncManager } from './core/sync';
27
+ export { createServer, startServer } from './server/api';
28
+ export { startMCPServer } from './server/mcp';
29
+ export * from './core/types';
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,cAAc,cAAc,CAAC"}