clawck 0.3.0 → 0.4.2

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 (70) hide show
  1. package/README.md +44 -15
  2. package/dist/cli/index.js +466 -43
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/atp.d.ts +31 -0
  5. package/dist/core/atp.d.ts.map +1 -0
  6. package/dist/core/atp.js +96 -0
  7. package/dist/core/atp.js.map +1 -0
  8. package/dist/core/benchmarks.d.ts +20 -0
  9. package/dist/core/benchmarks.d.ts.map +1 -0
  10. package/dist/core/benchmarks.js +86 -0
  11. package/dist/core/benchmarks.js.map +1 -0
  12. package/dist/core/clawck.d.ts +26 -1
  13. package/dist/core/clawck.d.ts.map +1 -1
  14. package/dist/core/clawck.js +105 -6
  15. package/dist/core/clawck.js.map +1 -1
  16. package/dist/core/database.d.ts +15 -1
  17. package/dist/core/database.d.ts.map +1 -1
  18. package/dist/core/database.js +157 -7
  19. package/dist/core/database.js.map +1 -1
  20. package/dist/core/personal.d.ts +26 -0
  21. package/dist/core/personal.d.ts.map +1 -0
  22. package/dist/core/personal.js +60 -0
  23. package/dist/core/personal.js.map +1 -0
  24. package/dist/core/runtime.d.ts +17 -0
  25. package/dist/core/runtime.d.ts.map +1 -0
  26. package/dist/core/runtime.js +43 -0
  27. package/dist/core/runtime.js.map +1 -0
  28. package/dist/core/types.d.ts +62 -2
  29. package/dist/core/types.d.ts.map +1 -1
  30. package/dist/core/types.js +9 -2
  31. package/dist/core/types.js.map +1 -1
  32. package/dist/hooks/adapters.d.ts.map +1 -1
  33. package/dist/hooks/adapters.js +14 -0
  34. package/dist/hooks/adapters.js.map +1 -1
  35. package/dist/hooks/handler.d.ts.map +1 -1
  36. package/dist/hooks/handler.js +4 -1
  37. package/dist/hooks/handler.js.map +1 -1
  38. package/dist/hooks/install.d.ts.map +1 -1
  39. package/dist/hooks/install.js +12 -4
  40. package/dist/hooks/install.js.map +1 -1
  41. package/dist/hooks/types.d.ts +3 -0
  42. package/dist/hooks/types.d.ts.map +1 -1
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +19 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/reports/html.d.ts +2 -1
  48. package/dist/reports/html.d.ts.map +1 -1
  49. package/dist/reports/html.js +53 -21
  50. package/dist/reports/html.js.map +1 -1
  51. package/dist/reports/pdf.d.ts +2 -1
  52. package/dist/reports/pdf.d.ts.map +1 -1
  53. package/dist/reports/pdf.js +26 -18
  54. package/dist/reports/pdf.js.map +1 -1
  55. package/dist/reports/periods.d.ts +18 -0
  56. package/dist/reports/periods.d.ts.map +1 -0
  57. package/dist/reports/periods.js +55 -0
  58. package/dist/reports/periods.js.map +1 -0
  59. package/dist/server/api.d.ts.map +1 -1
  60. package/dist/server/api.js +140 -2
  61. package/dist/server/api.js.map +1 -1
  62. package/dist/server/mcp.js +1 -1
  63. package/docs/atp-schema.json +298 -0
  64. package/docs/atp-spec-v0.2.md +216 -0
  65. package/docs/benchmarks-sources.md +202 -0
  66. package/docs/platform-testing-guide.md +423 -0
  67. package/docs/skills/clawck-usage.md +1 -1
  68. package/docs/snippets/claude-md.txt +16 -7
  69. package/docs/snippets/hooks-claude.json +12 -4
  70. 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.3.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', '7')
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
- const days = parseInt(opts.days) || 7;
186
- const to = new Date().toISOString();
187
- const from = new Date(Date.now() - days * 86400000).toISOString();
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 (program.opts().json) {
218
- console.log(JSON.stringify(ts));
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 📋 Clawck Timesheet — Last ${days} days`);
223
- console.log(` ${''.repeat(50)}`);
224
- console.log(` ⏱️ Agent hours: ${ts.total_agent_hours.toFixed(2)} hrs`);
225
- console.log(` 👤 Human equiv: ${ts.total_human_equiv_hours.toFixed(2)} hrs`);
226
- console.log(` 💰 Agent cost: $${ts.total_cost_usd.toFixed(2)}`);
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
- if (ts.by_agent.length > 0) {
238
- console.log(`\n 🤖 By Agent:`);
239
- for (const a of ts.by_agent) {
240
- console.log(` • ${a.agent} (${a.model}): ${a.agent_hours.toFixed(2)}h, ${a.success_rate}% success`);
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.detailed) {
244
- const entries = clawck.query({ client: opts.client, project: opts.project, agent: opts.agent, from, to, limit: 500 });
245
- console.log(`\n 📝 Entries:`);
246
- printEntryTable(entries);
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(` Duration: ${duration_minutes !== null ? formatDuration(duration_minutes) : 'unknown'} Status: ${entry.status}`);
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 = ['cubi-research-01', 'cubi-writer-02', 'cubi-coder-03', 'cubi-analyst-04', 'cubi-outreach-05'];
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: '0.1.0',
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 CSV')
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 csv)', 'json')
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
  }