clawck 0.3.0 → 0.4.1
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/README.md +44 -15
- package/dist/cli/index.js +466 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/core/atp.d.ts +31 -0
- package/dist/core/atp.d.ts.map +1 -0
- package/dist/core/atp.js +96 -0
- package/dist/core/atp.js.map +1 -0
- package/dist/core/benchmarks.d.ts +20 -0
- package/dist/core/benchmarks.d.ts.map +1 -0
- package/dist/core/benchmarks.js +86 -0
- package/dist/core/benchmarks.js.map +1 -0
- package/dist/core/clawck.d.ts +26 -1
- package/dist/core/clawck.d.ts.map +1 -1
- package/dist/core/clawck.js +101 -6
- package/dist/core/clawck.js.map +1 -1
- package/dist/core/database.d.ts +15 -1
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +157 -7
- package/dist/core/database.js.map +1 -1
- package/dist/core/personal.d.ts +26 -0
- package/dist/core/personal.d.ts.map +1 -0
- package/dist/core/personal.js +60 -0
- package/dist/core/personal.js.map +1 -0
- package/dist/core/runtime.d.ts +17 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +43 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/types.d.ts +62 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -2
- package/dist/core/types.js.map +1 -1
- package/dist/hooks/adapters.d.ts.map +1 -1
- package/dist/hooks/adapters.js +14 -0
- package/dist/hooks/adapters.js.map +1 -1
- package/dist/hooks/handler.d.ts.map +1 -1
- package/dist/hooks/handler.js +4 -1
- package/dist/hooks/handler.js.map +1 -1
- package/dist/hooks/install.d.ts.map +1 -1
- package/dist/hooks/install.js +12 -4
- package/dist/hooks/install.js.map +1 -1
- package/dist/hooks/types.d.ts +3 -0
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/reports/html.d.ts +2 -1
- package/dist/reports/html.d.ts.map +1 -1
- package/dist/reports/html.js +53 -21
- package/dist/reports/html.js.map +1 -1
- package/dist/reports/pdf.d.ts +2 -1
- package/dist/reports/pdf.d.ts.map +1 -1
- package/dist/reports/pdf.js +26 -18
- package/dist/reports/pdf.js.map +1 -1
- package/dist/reports/periods.d.ts +18 -0
- package/dist/reports/periods.d.ts.map +1 -0
- package/dist/reports/periods.js +55 -0
- package/dist/reports/periods.js.map +1 -0
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +140 -2
- package/dist/server/api.js.map +1 -1
- package/dist/server/mcp.js +1 -1
- package/docs/atp-schema.json +298 -0
- package/docs/atp-spec-v0.2.md +216 -0
- package/docs/benchmarks-sources.md +202 -0
- package/docs/platform-testing-guide.md +423 -0
- package/docs/skills/clawck-usage.md +1 -1
- package/docs/snippets/claude-md.txt +16 -7
- package/docs/snippets/hooks-claude.json +12 -4
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -57,13 +57,17 @@ const clawck_1 = require("../core/clawck");
|
|
|
57
57
|
const types_1 = require("../core/types");
|
|
58
58
|
const pdf_1 = require("../reports/pdf");
|
|
59
59
|
const html_1 = require("../reports/html");
|
|
60
|
+
const periods_1 = require("../reports/periods");
|
|
60
61
|
const patterns_1 = require("../core/patterns");
|
|
61
62
|
const hooks_1 = require("../hooks");
|
|
63
|
+
const benchmarks_1 = require("../core/benchmarks");
|
|
64
|
+
const atp_1 = require("../core/atp");
|
|
65
|
+
const types_2 = require("../core/types");
|
|
62
66
|
const program = new commander_1.Command();
|
|
63
67
|
program
|
|
64
68
|
.name('clawck')
|
|
65
69
|
.description('⏱️🦀 Clawck — Time tracking for AI agents')
|
|
66
|
-
.version('0.
|
|
70
|
+
.version('0.4.0')
|
|
67
71
|
.enablePositionalOptions()
|
|
68
72
|
.option('--json', 'Output as JSON (for scripting/pipelines)')
|
|
69
73
|
.option('-d, --dir <path>', 'Data directory (also: CLAWCK_DIR env var)');
|
|
@@ -133,7 +137,7 @@ program
|
|
|
133
137
|
.option('-d, --dir <path>', 'Data directory')
|
|
134
138
|
.action(async (opts) => {
|
|
135
139
|
const config = loadConfig(resolveDataDir(opts));
|
|
136
|
-
(0, mcp_1.startMCPServer)(config);
|
|
140
|
+
await (0, mcp_1.startMCPServer)(config);
|
|
137
141
|
});
|
|
138
142
|
// ─── Status ───────────────────────────────────────────────
|
|
139
143
|
program
|
|
@@ -168,40 +172,54 @@ program
|
|
|
168
172
|
clawck.close();
|
|
169
173
|
});
|
|
170
174
|
// ─── Report ───────────────────────────────────────────────
|
|
171
|
-
program
|
|
175
|
+
const reportCmd = program
|
|
172
176
|
.command('report')
|
|
173
177
|
.description('Show a timesheet summary')
|
|
174
178
|
.option('-d, --dir <path>', 'Data directory')
|
|
175
|
-
.option('--days <number>', 'Number of days to include'
|
|
179
|
+
.option('--days <number>', 'Number of days to include')
|
|
180
|
+
.option('--period <type>', 'Time period (day, week, month, year, custom)')
|
|
181
|
+
.option('--from <date>', 'Start date (YYYY-MM-DD or ISO)')
|
|
182
|
+
.option('--to <date>', 'End date (YYYY-MM-DD or ISO)')
|
|
183
|
+
.option('--style <type>', 'Report style (full, short, visual, text, table, calendar)', 'full')
|
|
176
184
|
.option('--client <name>', 'Filter by client')
|
|
177
185
|
.option('--project <name>', 'Filter by project')
|
|
178
186
|
.option('--agent <name>', 'Filter by agent')
|
|
179
187
|
.option('--format <type>', 'Output format (terminal, pdf, or html)', 'terminal')
|
|
180
|
-
.option('--output <path>', 'Output file path (for pdf format)')
|
|
188
|
+
.option('--output <path>', 'Output file path (for pdf/html format)')
|
|
181
189
|
.option('--detailed', 'Show individual entries')
|
|
190
|
+
.option('--save', 'Save report to database')
|
|
191
|
+
.option('--name <name>', 'Name for saved report')
|
|
182
192
|
.action(async (opts) => {
|
|
183
193
|
const config = loadConfig(resolveDataDir(opts));
|
|
184
194
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
195
|
+
// Resolve time period
|
|
196
|
+
const resolved = (0, periods_1.resolvePeriod)({
|
|
197
|
+
period: opts.period,
|
|
198
|
+
from: opts.from,
|
|
199
|
+
to: opts.to,
|
|
200
|
+
days: opts.days ? parseInt(opts.days) : undefined,
|
|
201
|
+
});
|
|
202
|
+
const { from, to, period } = resolved;
|
|
203
|
+
// --detailed maps to style='table' when no explicit --style
|
|
204
|
+
const style = opts.detailed && opts.style === 'full' ? 'table' : opts.style;
|
|
188
205
|
const ts = clawck.timesheet(from, to, {
|
|
189
206
|
client: opts.client,
|
|
190
207
|
project: opts.project,
|
|
191
208
|
agent: opts.agent,
|
|
192
209
|
});
|
|
210
|
+
const periodLabel = opts.period || (opts.days ? `${opts.days} days` : period);
|
|
211
|
+
let reportContent = '';
|
|
193
212
|
if (opts.format === 'html') {
|
|
194
213
|
const today = new Date().toISOString().split('T')[0];
|
|
195
214
|
const outputPath = opts.output || `clawck-report-${today}.html`;
|
|
196
215
|
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
197
216
|
const rawEntries = clawck.query({ from, to, client: opts.client, project: opts.project, agent: opts.agent, limit: 10000 });
|
|
198
|
-
const html = (0, html_1.generateTimesheetHTML)(ts, { dateRange, clientName: opts.client, rawEntries });
|
|
217
|
+
const html = (0, html_1.generateTimesheetHTML)(ts, { dateRange, clientName: opts.client, rawEntries, style });
|
|
218
|
+
reportContent = html;
|
|
199
219
|
fs_1.default.writeFileSync(outputPath, html);
|
|
200
220
|
console.log(` HTML report saved to: ${outputPath}`);
|
|
201
|
-
clawck.close();
|
|
202
|
-
return;
|
|
203
221
|
}
|
|
204
|
-
if (opts.format === 'pdf') {
|
|
222
|
+
else if (opts.format === 'pdf') {
|
|
205
223
|
const today = new Date().toISOString().split('T')[0];
|
|
206
224
|
const outputPath = opts.output || `clawck-report-${today}.pdf`;
|
|
207
225
|
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
@@ -209,43 +227,165 @@ program
|
|
|
209
227
|
clientName: opts.client,
|
|
210
228
|
dateRange,
|
|
211
229
|
outputPath,
|
|
230
|
+
style,
|
|
212
231
|
});
|
|
232
|
+
reportContent = fs_1.default.readFileSync(outputPath).toString('base64');
|
|
213
233
|
console.log(` PDF report saved to: ${outputPath}`);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// Terminal output
|
|
237
|
+
if (program.opts().json) {
|
|
238
|
+
reportContent = JSON.stringify(ts);
|
|
239
|
+
console.log(reportContent);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const totalAgentRuntimeMin = ts.entries.reduce((s, r) => s + (r.agent_runtime_minutes || 0), 0);
|
|
243
|
+
if (style === 'table') {
|
|
244
|
+
const entries = clawck.query({ client: opts.client, project: opts.project, agent: opts.agent, from, to, limit: 500 });
|
|
245
|
+
printEntryTable(entries);
|
|
246
|
+
}
|
|
247
|
+
else if (style === 'short') {
|
|
248
|
+
console.log(`\n 📋 Clawck Timesheet — ${periodLabel}`);
|
|
249
|
+
console.log(` ${'─'.repeat(50)}`);
|
|
250
|
+
console.log(` ⏱️ Wall-clock hours: ${ts.total_agent_hours.toFixed(2)} hrs`);
|
|
251
|
+
if (totalAgentRuntimeMin > 0) {
|
|
252
|
+
console.log(` 🤖 Agent runtime: ${formatDuration(totalAgentRuntimeMin)} (estimated)`);
|
|
253
|
+
}
|
|
254
|
+
console.log(` 👤 Human equiv: ${ts.total_human_equiv_hours.toFixed(2)} hrs`);
|
|
255
|
+
console.log(` 💰 Agent cost: $${ts.total_cost_usd.toFixed(2)}`);
|
|
256
|
+
console.log(` 💚 Est. savings: $${ts.total_savings_usd.toFixed(0)}`);
|
|
257
|
+
console.log(` 🔢 Total entries: ${ts.total_entries}`);
|
|
258
|
+
console.log(` 🪙 Total tokens: ${ts.total_tokens.toLocaleString()}`);
|
|
259
|
+
console.log('');
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
// full, text, visual, calendar all fall back to full terminal output
|
|
263
|
+
console.log(`\n 📋 Clawck Timesheet — ${periodLabel}`);
|
|
264
|
+
console.log(` ${'─'.repeat(50)}`);
|
|
265
|
+
console.log(` ⏱️ Wall-clock hours: ${ts.total_agent_hours.toFixed(2)} hrs`);
|
|
266
|
+
if (totalAgentRuntimeMin > 0) {
|
|
267
|
+
console.log(` 🤖 Agent runtime: ${formatDuration(totalAgentRuntimeMin)} (estimated)`);
|
|
268
|
+
}
|
|
269
|
+
console.log(` 👤 Human equiv: ${ts.total_human_equiv_hours.toFixed(2)} hrs`);
|
|
270
|
+
console.log(` 💰 Agent cost: $${ts.total_cost_usd.toFixed(2)}`);
|
|
271
|
+
console.log(` 💚 Est. savings: $${ts.total_savings_usd.toFixed(0)}`);
|
|
272
|
+
console.log(` 🔢 Total entries: ${ts.total_entries}`);
|
|
273
|
+
console.log(` 🪙 Total tokens: ${ts.total_tokens.toLocaleString()}`);
|
|
274
|
+
if (ts.by_project.length > 0) {
|
|
275
|
+
console.log(`\n 📁 By Project:`);
|
|
276
|
+
for (const p of ts.by_project) {
|
|
277
|
+
const bar = '█'.repeat(Math.max(1, Math.round(p.agent_hours / (ts.total_agent_hours || 1) * 20)));
|
|
278
|
+
console.log(` ${bar} ${p.project} (${p.client}): ${p.agent_hours.toFixed(2)}h → ${p.human_equiv_hours.toFixed(2)}h human equiv`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (ts.by_agent.length > 0) {
|
|
282
|
+
console.log(`\n 🤖 By Agent:`);
|
|
283
|
+
for (const a of ts.by_agent) {
|
|
284
|
+
console.log(` • ${a.agent} (${a.model}): ${a.agent_hours.toFixed(2)}h, ${a.success_rate}% success`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
console.log('');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Save report if requested
|
|
292
|
+
if (opts.save) {
|
|
293
|
+
const metadata = {
|
|
294
|
+
filters: { client: opts.client, project: opts.project, agent: opts.agent },
|
|
295
|
+
total_entries: ts.total_entries,
|
|
296
|
+
total_agent_hours: ts.total_agent_hours,
|
|
297
|
+
total_cost_usd: ts.total_cost_usd,
|
|
298
|
+
total_savings_usd: ts.total_savings_usd,
|
|
299
|
+
};
|
|
300
|
+
const saved = clawck.saveReport({
|
|
301
|
+
name: opts.name || `Report ${new Date().toISOString().split('T')[0]}`,
|
|
302
|
+
period,
|
|
303
|
+
period_start: from,
|
|
304
|
+
period_end: to,
|
|
305
|
+
style,
|
|
306
|
+
format: opts.format,
|
|
307
|
+
content: reportContent || JSON.stringify(ts),
|
|
308
|
+
metadata,
|
|
309
|
+
});
|
|
310
|
+
console.log(` Report saved: ${saved.id.slice(0, 8)}`);
|
|
311
|
+
}
|
|
312
|
+
clawck.close();
|
|
313
|
+
});
|
|
314
|
+
// ─── Report Subcommands ─────────────────────────────────
|
|
315
|
+
reportCmd
|
|
316
|
+
.command('list')
|
|
317
|
+
.description('List saved reports')
|
|
318
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
319
|
+
.option('--limit <n>', 'Max reports to show', '20')
|
|
320
|
+
.action(async (opts) => {
|
|
321
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
322
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
323
|
+
const reports = clawck.listReports(parseInt(opts.limit) || 20);
|
|
324
|
+
if (program.opts().json) {
|
|
325
|
+
console.log(JSON.stringify(reports));
|
|
214
326
|
clawck.close();
|
|
215
327
|
return;
|
|
216
328
|
}
|
|
217
|
-
if (
|
|
218
|
-
console.log(
|
|
329
|
+
if (reports.length === 0) {
|
|
330
|
+
console.log('\n No saved reports.\n');
|
|
219
331
|
clawck.close();
|
|
220
332
|
return;
|
|
221
333
|
}
|
|
222
|
-
console.log(`\n
|
|
223
|
-
console.log(` ${'
|
|
224
|
-
console.log(`
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log(` 💚 Est. savings: $${ts.total_savings_usd.toFixed(0)}`);
|
|
228
|
-
console.log(` 🔢 Total entries: ${ts.total_entries}`);
|
|
229
|
-
console.log(` 🪙 Total tokens: ${ts.total_tokens.toLocaleString()}`);
|
|
230
|
-
if (ts.by_project.length > 0) {
|
|
231
|
-
console.log(`\n 📁 By Project:`);
|
|
232
|
-
for (const p of ts.by_project) {
|
|
233
|
-
const bar = '█'.repeat(Math.max(1, Math.round(p.agent_hours / (ts.total_agent_hours || 1) * 20)));
|
|
234
|
-
console.log(` ${bar} ${p.project} (${p.client}): ${p.agent_hours.toFixed(2)}h → ${p.human_equiv_hours.toFixed(2)}h human equiv`);
|
|
235
|
-
}
|
|
334
|
+
console.log(`\n Saved Reports:`);
|
|
335
|
+
console.log(` ${'ID'.padEnd(10)} ${'Name'.padEnd(30)} ${'Period'.padEnd(8)} ${'Style'.padEnd(10)} ${'Format'.padEnd(10)} ${'Date'}`);
|
|
336
|
+
console.log(` ${'─'.repeat(80)}`);
|
|
337
|
+
for (const r of reports) {
|
|
338
|
+
console.log(` ${r.id.slice(0, 8).padEnd(10)} ${(r.name || '-').slice(0, 28).padEnd(30)} ${r.period.padEnd(8)} ${r.style.padEnd(10)} ${r.format.padEnd(10)} ${r.created_at.split('T')[0]}`);
|
|
236
339
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
340
|
+
console.log('');
|
|
341
|
+
clawck.close();
|
|
342
|
+
});
|
|
343
|
+
reportCmd
|
|
344
|
+
.command('show <id>')
|
|
345
|
+
.description('Retrieve a saved report')
|
|
346
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
347
|
+
.option('--output <path>', 'Write content to file')
|
|
348
|
+
.action(async (id, opts) => {
|
|
349
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
350
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
351
|
+
const report = clawck.getReport(id);
|
|
352
|
+
if (!report) {
|
|
353
|
+
console.error(` Report not found: ${id}`);
|
|
354
|
+
clawck.close();
|
|
355
|
+
process.exit(1);
|
|
242
356
|
}
|
|
243
|
-
if (opts.
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
357
|
+
if (opts.output) {
|
|
358
|
+
const content = typeof report.content === 'string' ? report.content : report.content;
|
|
359
|
+
fs_1.default.writeFileSync(opts.output, content);
|
|
360
|
+
console.log(` Report written to: ${opts.output}`);
|
|
361
|
+
}
|
|
362
|
+
else if (program.opts().json) {
|
|
363
|
+
console.log(JSON.stringify(report));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.log(typeof report.content === 'string' ? report.content : '[binary content — use --output to save]');
|
|
367
|
+
}
|
|
368
|
+
clawck.close();
|
|
369
|
+
});
|
|
370
|
+
reportCmd
|
|
371
|
+
.command('delete <id>')
|
|
372
|
+
.description('Delete a saved report')
|
|
373
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
374
|
+
.action(async (id, opts) => {
|
|
375
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
376
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
377
|
+
const deleted = clawck.deleteReport(id);
|
|
378
|
+
if (!deleted) {
|
|
379
|
+
console.error(` Report not found: ${id}`);
|
|
380
|
+
clawck.close();
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
if (program.opts().json) {
|
|
384
|
+
console.log(JSON.stringify({ ok: true }));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
console.log(` Report deleted.`);
|
|
247
388
|
}
|
|
248
|
-
console.log('');
|
|
249
389
|
clawck.close();
|
|
250
390
|
});
|
|
251
391
|
// ─── Start ───────────────────────────────────────────────
|
|
@@ -324,7 +464,10 @@ program
|
|
|
324
464
|
}
|
|
325
465
|
else {
|
|
326
466
|
console.log(` Stopped: ${entry.task}`);
|
|
327
|
-
console.log(`
|
|
467
|
+
console.log(` Wall clock: ${duration_minutes !== null ? formatDuration(duration_minutes) : 'unknown'} Status: ${entry.status}`);
|
|
468
|
+
if (entry.agent_runtime_ms != null) {
|
|
469
|
+
console.log(` Agent runtime (est.): ${formatDuration(entry.agent_runtime_ms / 60000)}`);
|
|
470
|
+
}
|
|
328
471
|
}
|
|
329
472
|
clawck.close();
|
|
330
473
|
});
|
|
@@ -630,7 +773,7 @@ program
|
|
|
630
773
|
const config = loadConfig(resolveDataDir(opts));
|
|
631
774
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
632
775
|
const count = parseInt(opts.count) || 25;
|
|
633
|
-
const agents = ['
|
|
776
|
+
const agents = ['research-agent-01', 'writer-agent-02', 'coder-agent-03', 'analyst-agent-04', 'outreach-agent-05'];
|
|
634
777
|
const models = ['claude-sonnet-4-20250514', 'claude-haiku-4-5-20251001', 'gpt-4o', 'gemini-2.0-flash'];
|
|
635
778
|
const clients = ['acme-corp', 'globex-inc', 'initech', 'hooli', 'umbrella-co'];
|
|
636
779
|
const projects = ['website-rebuild', 'seo-content', 'grant-research', 'data-migration', 'email-campaigns', 'api-v2'];
|
|
@@ -719,7 +862,7 @@ program
|
|
|
719
862
|
summary: 'Task failed - auto-generated seed entry',
|
|
720
863
|
tags: ['seed', 'failed'],
|
|
721
864
|
source: 'clawck',
|
|
722
|
-
spec_version:
|
|
865
|
+
spec_version: types_1.SPEC_VERSION,
|
|
723
866
|
});
|
|
724
867
|
}
|
|
725
868
|
console.log(`\n ⏱️🦀 Seeded ${count + 6} entries into Clawck!`);
|
|
@@ -846,9 +989,9 @@ program
|
|
|
846
989
|
// ─── Export ──────────────────────────────────────────────
|
|
847
990
|
program
|
|
848
991
|
.command('export')
|
|
849
|
-
.description('Export time entries as JSON or
|
|
992
|
+
.description('Export time entries as JSON, CSV, or ATP envelope')
|
|
850
993
|
.option('-d, --dir <path>', 'Data directory')
|
|
851
|
-
.option('--format <type>', 'Output format (json or
|
|
994
|
+
.option('--format <type>', 'Output format (json, csv, or atp)', 'json')
|
|
852
995
|
.option('--days <number>', 'Number of days to include', '7')
|
|
853
996
|
.option('--client <name>', 'Filter by client')
|
|
854
997
|
.option('--project <name>', 'Filter by project')
|
|
@@ -867,6 +1010,13 @@ program
|
|
|
867
1010
|
to,
|
|
868
1011
|
limit: 10000,
|
|
869
1012
|
});
|
|
1013
|
+
if (opts.format === 'atp') {
|
|
1014
|
+
const baselines = clawck.getBaselines();
|
|
1015
|
+
const envelope = (0, atp_1.exportATP)(entries, benchmarks_1.INDUSTRY_BENCHMARKS, baselines);
|
|
1016
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
1017
|
+
clawck.close();
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
870
1020
|
if (opts.format === 'csv') {
|
|
871
1021
|
const csvEscape = (val) => {
|
|
872
1022
|
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
|
@@ -905,6 +1055,269 @@ program
|
|
|
905
1055
|
}
|
|
906
1056
|
clawck.close();
|
|
907
1057
|
});
|
|
1058
|
+
// ─── Benchmark ──────────────────────────────────────────
|
|
1059
|
+
const benchmark = program
|
|
1060
|
+
.command('benchmark')
|
|
1061
|
+
.description('View industry benchmarks for task categories');
|
|
1062
|
+
benchmark
|
|
1063
|
+
.command('list')
|
|
1064
|
+
.description('List all industry benchmarks')
|
|
1065
|
+
.action(async () => {
|
|
1066
|
+
if (program.opts().json) {
|
|
1067
|
+
console.log(JSON.stringify(benchmarks_1.INDUSTRY_BENCHMARKS));
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
console.log(`\n Industry Benchmarks (human median times):`);
|
|
1071
|
+
console.log(` ${'─'.repeat(80)}`);
|
|
1072
|
+
console.log(` ${'Category'.padEnd(15)} ${'Task Type'.padEnd(25)} ${'Median'.padEnd(10)} ${'P25'.padEnd(10)} ${'P75'.padEnd(10)} Source`);
|
|
1073
|
+
console.log(` ${'─'.repeat(80)}`);
|
|
1074
|
+
for (const b of benchmarks_1.INDUSTRY_BENCHMARKS) {
|
|
1075
|
+
console.log(` ${b.category.padEnd(15)} ${b.task_type.padEnd(25)} ${formatDuration(b.human_median_minutes).padEnd(10)} ${formatDuration(b.human_p25_minutes).padEnd(10)} ${formatDuration(b.human_p75_minutes).padEnd(10)} ${b.source}`);
|
|
1076
|
+
}
|
|
1077
|
+
console.log('');
|
|
1078
|
+
});
|
|
1079
|
+
benchmark
|
|
1080
|
+
.command('category <name>')
|
|
1081
|
+
.description('Show benchmarks for a specific category')
|
|
1082
|
+
.action(async (name) => {
|
|
1083
|
+
const catBenchmarks = benchmarks_1.INDUSTRY_BENCHMARKS.filter(b => b.category === name);
|
|
1084
|
+
if (catBenchmarks.length === 0) {
|
|
1085
|
+
console.error(` No benchmarks found for category: ${name}`);
|
|
1086
|
+
console.error(` Available: ${types_2.TASK_CATEGORIES.join(', ')}`);
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
if (program.opts().json) {
|
|
1090
|
+
console.log(JSON.stringify(catBenchmarks));
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
console.log(`\n Benchmarks for "${name}":`);
|
|
1094
|
+
for (const b of catBenchmarks) {
|
|
1095
|
+
console.log(` - ${b.task_type}`);
|
|
1096
|
+
console.log(` Median: ${formatDuration(b.human_median_minutes)} Fast: ${formatDuration(b.human_p25_minutes)} Slow: ${formatDuration(b.human_p75_minutes)}`);
|
|
1097
|
+
console.log(` Source: ${b.source} (${b.year})`);
|
|
1098
|
+
}
|
|
1099
|
+
console.log('');
|
|
1100
|
+
});
|
|
1101
|
+
// ─── Baseline ───────────────────────────────────────────
|
|
1102
|
+
const baseline = program
|
|
1103
|
+
.command('baseline')
|
|
1104
|
+
.description('Manage personal time baselines');
|
|
1105
|
+
baseline
|
|
1106
|
+
.command('add')
|
|
1107
|
+
.description('Add a personal baseline')
|
|
1108
|
+
.requiredOption('--category <type>', 'Task category')
|
|
1109
|
+
.requiredOption('--task-type <type>', 'Task type identifier')
|
|
1110
|
+
.requiredOption('--minutes <n>', 'How long this takes you (minutes)', parseFloat)
|
|
1111
|
+
.option('--description <text>', 'Description of the task')
|
|
1112
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
1113
|
+
.action(async (opts) => {
|
|
1114
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
1115
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
1116
|
+
const bl = clawck.addBaseline({
|
|
1117
|
+
category: opts.category,
|
|
1118
|
+
task_type: opts.taskType,
|
|
1119
|
+
description: opts.description || '',
|
|
1120
|
+
my_minutes: opts.minutes,
|
|
1121
|
+
});
|
|
1122
|
+
if (program.opts().json) {
|
|
1123
|
+
console.log(JSON.stringify(bl));
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
console.log(` Added baseline: ${bl.task_type} (${bl.category}) — ${formatDuration(bl.my_minutes)}`);
|
|
1127
|
+
console.log(` ID: ${bl.id.slice(0, 8)}`);
|
|
1128
|
+
}
|
|
1129
|
+
clawck.close();
|
|
1130
|
+
});
|
|
1131
|
+
baseline
|
|
1132
|
+
.command('list')
|
|
1133
|
+
.description('List all personal baselines')
|
|
1134
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
1135
|
+
.action(async (opts) => {
|
|
1136
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
1137
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
1138
|
+
const baselines = clawck.getBaselines();
|
|
1139
|
+
if (program.opts().json) {
|
|
1140
|
+
console.log(JSON.stringify(baselines));
|
|
1141
|
+
clawck.close();
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (baselines.length === 0) {
|
|
1145
|
+
console.log('\n No personal baselines set.');
|
|
1146
|
+
console.log(' Add one: clawck baseline add --category code --task-type code_review --minutes 30\n');
|
|
1147
|
+
clawck.close();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
console.log(`\n Personal Baselines:`);
|
|
1151
|
+
for (const b of baselines) {
|
|
1152
|
+
console.log(` ${b.id.slice(0, 8)} ${b.category.padEnd(14)} ${b.task_type.padEnd(25)} ${formatDuration(b.my_minutes).padEnd(8)} ${b.description}`);
|
|
1153
|
+
}
|
|
1154
|
+
console.log('');
|
|
1155
|
+
clawck.close();
|
|
1156
|
+
});
|
|
1157
|
+
baseline
|
|
1158
|
+
.command('remove <id>')
|
|
1159
|
+
.description('Remove a personal baseline')
|
|
1160
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
1161
|
+
.action(async (id, opts) => {
|
|
1162
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
1163
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
1164
|
+
const deleted = clawck.removeBaseline(id);
|
|
1165
|
+
if (!deleted) {
|
|
1166
|
+
console.error(` Baseline not found: ${id}`);
|
|
1167
|
+
clawck.close();
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
if (program.opts().json) {
|
|
1171
|
+
console.log(JSON.stringify({ ok: true }));
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
console.log(` Removed baseline: ${id}`);
|
|
1175
|
+
}
|
|
1176
|
+
clawck.close();
|
|
1177
|
+
});
|
|
1178
|
+
// ─── Demo ───────────────────────────────────────────────
|
|
1179
|
+
program
|
|
1180
|
+
.command('demo')
|
|
1181
|
+
.description('Generate demo data, reports, and start interactive dashboard')
|
|
1182
|
+
.option('-d, --dir <path>', 'Demo data directory', '.clawck-demo')
|
|
1183
|
+
.action(async (opts) => {
|
|
1184
|
+
const demoDir = path_1.default.resolve(opts.dir);
|
|
1185
|
+
if (!fs_1.default.existsSync(demoDir))
|
|
1186
|
+
fs_1.default.mkdirSync(demoDir, { recursive: true });
|
|
1187
|
+
const config = loadConfig(demoDir);
|
|
1188
|
+
config.data_dir = demoDir;
|
|
1189
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
1190
|
+
const { v4: uuid } = await Promise.resolve().then(() => __importStar(require('uuid')));
|
|
1191
|
+
const agents = ['research-agent-01', 'writer-agent-02', 'coder-agent-03', 'analyst-agent-04', 'outreach-agent-05'];
|
|
1192
|
+
const models = ['claude-sonnet-4-20250514', 'claude-haiku-4-5-20251001', 'gpt-4o', 'gemini-2.0-flash'];
|
|
1193
|
+
const clients = ['acme-corp', 'globex-inc', 'initech', 'hooli', 'umbrella-co'];
|
|
1194
|
+
const projects = ['website-rebuild', 'seo-content', 'grant-research', 'data-migration', 'email-campaigns', 'api-v2'];
|
|
1195
|
+
const categories = ['research', 'content', 'code', 'data_entry', 'analysis', 'communication', 'testing', 'design', 'planning', 'other'];
|
|
1196
|
+
const tasks = [
|
|
1197
|
+
'Research competitor pricing strategies', 'Write blog post about AI automation',
|
|
1198
|
+
'Refactor authentication module', 'Migrate legacy CSV data to new schema',
|
|
1199
|
+
'Analyze Q3 customer churn patterns', 'Draft outreach emails for partnership',
|
|
1200
|
+
'Write unit tests for payment flow', 'Design email template for newsletter',
|
|
1201
|
+
'Find relevant grant opportunities', 'Compile industry benchmark report',
|
|
1202
|
+
'Update API documentation', 'Generate social media content calendar',
|
|
1203
|
+
'Review and optimize database queries', 'Create investor pitch deck outline',
|
|
1204
|
+
'Summarize recent industry news', 'Plan sprint roadmap for Q4',
|
|
1205
|
+
];
|
|
1206
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
1207
|
+
// Seed 35 completed entries across 14 days
|
|
1208
|
+
for (let i = 0; i < 35; i++) {
|
|
1209
|
+
const daysAgo = Math.random() * 14;
|
|
1210
|
+
const durationMin = 5 + Math.random() * 120;
|
|
1211
|
+
const tokensIn = Math.round(1000 + Math.random() * 50000);
|
|
1212
|
+
const tokensOut = Math.round(500 + Math.random() * 20000);
|
|
1213
|
+
const toolCalls = Math.round(Math.random() * 15);
|
|
1214
|
+
const category = i < categories.length ? categories[i] : pick(categories);
|
|
1215
|
+
const end = new Date(Date.now() - daysAgo * 86400000);
|
|
1216
|
+
const start = new Date(end.getTime() - durationMin * 60000);
|
|
1217
|
+
const model = pick(models);
|
|
1218
|
+
clawck.upsert({
|
|
1219
|
+
id: uuid(),
|
|
1220
|
+
task: pick(tasks),
|
|
1221
|
+
project: i < projects.length ? projects[i] : pick(projects),
|
|
1222
|
+
client: i < clients.length ? clients[i] : pick(clients),
|
|
1223
|
+
category,
|
|
1224
|
+
agent: i < agents.length ? agents[i] : pick(agents),
|
|
1225
|
+
model,
|
|
1226
|
+
start: start.toISOString(),
|
|
1227
|
+
end: end.toISOString(),
|
|
1228
|
+
status: 'completed',
|
|
1229
|
+
tokens_in: tokensIn,
|
|
1230
|
+
tokens_out: tokensOut,
|
|
1231
|
+
cost_usd: Math.round((tokensIn * 0.000003 + tokensOut * 0.000015) * 10000) / 10000,
|
|
1232
|
+
tool_calls: toolCalls,
|
|
1233
|
+
summary: 'Demo entry',
|
|
1234
|
+
tags: ['demo'],
|
|
1235
|
+
source: 'clawck-demo',
|
|
1236
|
+
spec_version: '0.2.0',
|
|
1237
|
+
approved: Math.random() < 0.6,
|
|
1238
|
+
agent_runtime_ms: Math.round((tokensOut / 80) * 1000 + toolCalls * 2000),
|
|
1239
|
+
wall_clock_ms: Math.round(durationMin * 60000),
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
// Add running entries
|
|
1243
|
+
for (let i = 0; i < 3; i++) {
|
|
1244
|
+
clawck.start({ task: pick(tasks), project: pick(projects), client: pick(clients), category: pick(categories), agent: pick(agents), model: pick(models), tags: ['demo'] });
|
|
1245
|
+
}
|
|
1246
|
+
// Add failed entries
|
|
1247
|
+
for (let i = 0; i < 3; i++) {
|
|
1248
|
+
const start = new Date(Date.now() - Math.random() * 7 * 86400000);
|
|
1249
|
+
const end = new Date(start.getTime() + (10 + Math.random() * 30) * 60000);
|
|
1250
|
+
clawck.upsert({
|
|
1251
|
+
id: uuid(), task: pick(tasks), project: pick(projects), client: pick(clients),
|
|
1252
|
+
category: pick(categories), agent: pick(agents), model: pick(models),
|
|
1253
|
+
start: start.toISOString(), end: end.toISOString(), status: 'failed',
|
|
1254
|
+
tokens_in: Math.round(500 + Math.random() * 5000), tokens_out: Math.round(100 + Math.random() * 1000),
|
|
1255
|
+
cost_usd: Math.round(Math.random() * 0.05 * 10000) / 10000, tool_calls: Math.round(Math.random() * 5),
|
|
1256
|
+
summary: 'Task failed', tags: ['demo', 'failed'], source: 'clawck-demo', spec_version: '0.2.0',
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
// Add personal baselines
|
|
1260
|
+
const baselineData = [
|
|
1261
|
+
{ category: 'code', task_type: 'code_review', description: 'Review a pull request', my_minutes: 30 },
|
|
1262
|
+
{ category: 'content', task_type: 'blog_post', description: 'Write a 1000-word blog post', my_minutes: 180 },
|
|
1263
|
+
{ category: 'research', task_type: 'competitive_analysis', description: 'Analyze competitor landscape', my_minutes: 360 },
|
|
1264
|
+
{ category: 'testing', task_type: 'unit_tests', description: 'Write unit tests for a module', my_minutes: 120 },
|
|
1265
|
+
{ category: 'communication', task_type: 'meeting_summary', description: 'Summarize a 1-hour meeting', my_minutes: 30 },
|
|
1266
|
+
{ category: 'planning', task_type: 'sprint_planning', description: 'Plan a 2-week sprint', my_minutes: 120 },
|
|
1267
|
+
];
|
|
1268
|
+
for (const bl of baselineData) {
|
|
1269
|
+
clawck.addBaseline(bl);
|
|
1270
|
+
}
|
|
1271
|
+
// Generate HTML report
|
|
1272
|
+
const to = new Date().toISOString();
|
|
1273
|
+
const from = new Date(Date.now() - 14 * 86400000).toISOString();
|
|
1274
|
+
const ts = clawck.timesheet(from, to);
|
|
1275
|
+
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
1276
|
+
const rawEntries = clawck.query({ from, to, limit: 10000 });
|
|
1277
|
+
const htmlPath = path_1.default.join(demoDir, 'clawck-demo-report.html');
|
|
1278
|
+
const html = (0, html_1.generateTimesheetHTML)(ts, { dateRange, rawEntries });
|
|
1279
|
+
fs_1.default.writeFileSync(htmlPath, html);
|
|
1280
|
+
// Generate PDF report
|
|
1281
|
+
const pdfPath = path_1.default.join(demoDir, 'clawck-demo-report.pdf');
|
|
1282
|
+
await (0, pdf_1.generateTimesheetPDF)(ts, { dateRange, outputPath: pdfPath });
|
|
1283
|
+
// Open HTML in browser
|
|
1284
|
+
const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1285
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1286
|
+
try {
|
|
1287
|
+
execSync(`${opener} "${htmlPath}"`, { stdio: 'ignore' });
|
|
1288
|
+
}
|
|
1289
|
+
catch { }
|
|
1290
|
+
// Start server
|
|
1291
|
+
const { startServer: startDemoServer } = await Promise.resolve().then(() => __importStar(require('../server/api')));
|
|
1292
|
+
const net = await Promise.resolve().then(() => __importStar(require('net')));
|
|
1293
|
+
const port = await new Promise((resolve) => {
|
|
1294
|
+
const srv = net.createServer();
|
|
1295
|
+
srv.listen(0, () => {
|
|
1296
|
+
const addr = srv.address();
|
|
1297
|
+
srv.close(() => resolve(addr.port));
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
config.port = port;
|
|
1301
|
+
console.log(`\n ⏱️🦀 Clawck Demo\n`);
|
|
1302
|
+
console.log(` Generated reports:`);
|
|
1303
|
+
console.log(` HTML: ${htmlPath} (opened in browser)`);
|
|
1304
|
+
console.log(` PDF: ${pdfPath}`);
|
|
1305
|
+
console.log(`\n Dashboard running at: http://localhost:${port}`);
|
|
1306
|
+
console.log(`\n Try these commands:`);
|
|
1307
|
+
console.log(` clawck list -d ${demoDir}`);
|
|
1308
|
+
console.log(` clawck report --format html -d ${demoDir}`);
|
|
1309
|
+
console.log(` clawck baseline list -d ${demoDir}`);
|
|
1310
|
+
console.log(` clawck benchmark list`);
|
|
1311
|
+
console.log(` clawck export --format atp -d ${demoDir}`);
|
|
1312
|
+
console.log(` clawck entries --approved -d ${demoDir}`);
|
|
1313
|
+
console.log(`\n Press Ctrl+C to stop the demo server.\n`);
|
|
1314
|
+
// Open dashboard
|
|
1315
|
+
try {
|
|
1316
|
+
execSync(`${opener} "http://localhost:${port}"`, { stdio: 'ignore' });
|
|
1317
|
+
}
|
|
1318
|
+
catch { }
|
|
1319
|
+
await startDemoServer(config);
|
|
1320
|
+
});
|
|
908
1321
|
// ─── Hook (runtime, singular) ────────────────────────────
|
|
909
1322
|
const hook = program
|
|
910
1323
|
.command('hook')
|
|
@@ -914,9 +1327,12 @@ hook
|
|
|
914
1327
|
.description('Start tracking — called by platform hooks')
|
|
915
1328
|
.option('-d, --dir <path>', 'Data directory')
|
|
916
1329
|
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
1330
|
+
.option('--verbose', 'Log received stdin JSON to stderr for debugging')
|
|
917
1331
|
.action(async (opts) => {
|
|
918
1332
|
try {
|
|
919
1333
|
const raw = await (0, hooks_1.readStdin)();
|
|
1334
|
+
if (opts.verbose)
|
|
1335
|
+
process.stderr.write(`clawck: hook start stdin: ${raw}\n`);
|
|
920
1336
|
let json = {};
|
|
921
1337
|
if (raw.trim()) {
|
|
922
1338
|
try {
|
|
@@ -928,6 +1344,8 @@ hook
|
|
|
928
1344
|
}
|
|
929
1345
|
const platform = opts.platform || undefined;
|
|
930
1346
|
const context = (0, hooks_1.normalize)(json, platform);
|
|
1347
|
+
if (opts.verbose)
|
|
1348
|
+
process.stderr.write(`clawck: hook start context: ${JSON.stringify(context)}\n`);
|
|
931
1349
|
const config = loadConfig(resolveDataDir(opts));
|
|
932
1350
|
await (0, hooks_1.handleHookStart)(config, context);
|
|
933
1351
|
}
|
|
@@ -942,9 +1360,12 @@ hook
|
|
|
942
1360
|
.description('Stop tracking — called by platform hooks')
|
|
943
1361
|
.option('-d, --dir <path>', 'Data directory')
|
|
944
1362
|
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
1363
|
+
.option('--verbose', 'Log received stdin JSON to stderr for debugging')
|
|
945
1364
|
.action(async (opts) => {
|
|
946
1365
|
try {
|
|
947
1366
|
const raw = await (0, hooks_1.readStdin)();
|
|
1367
|
+
if (opts.verbose)
|
|
1368
|
+
process.stderr.write(`clawck: hook stop stdin: ${raw}\n`);
|
|
948
1369
|
let json = {};
|
|
949
1370
|
if (raw.trim()) {
|
|
950
1371
|
try {
|
|
@@ -956,6 +1377,8 @@ hook
|
|
|
956
1377
|
}
|
|
957
1378
|
const platform = opts.platform || undefined;
|
|
958
1379
|
const context = (0, hooks_1.normalize)(json, platform);
|
|
1380
|
+
if (opts.verbose)
|
|
1381
|
+
process.stderr.write(`clawck: hook stop context: ${JSON.stringify(context)}\n`);
|
|
959
1382
|
const config = loadConfig(resolveDataDir(opts));
|
|
960
1383
|
await (0, hooks_1.handleHookStop)(config, context);
|
|
961
1384
|
}
|