clawck 0.1.3 → 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 +251 -174
- package/dist/cli/index.js +947 -87
- 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 +39 -3
- package/dist/core/clawck.d.ts.map +1 -1
- package/dist/core/clawck.js +140 -18
- package/dist/core/clawck.js.map +1 -1
- package/dist/core/database.d.ts +16 -1
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +177 -8
- package/dist/core/database.js.map +1 -1
- package/dist/core/patterns.d.ts +7 -0
- package/dist/core/patterns.d.ts.map +1 -0
- package/dist/core/patterns.js +36 -0
- package/dist/core/patterns.js.map +1 -0
- 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 +93 -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/core/webhooks.d.ts +22 -0
- package/dist/core/webhooks.d.ts.map +1 -0
- package/dist/core/webhooks.js +70 -0
- package/dist/core/webhooks.js.map +1 -0
- package/dist/hooks/adapters.d.ts +8 -0
- package/dist/hooks/adapters.d.ts.map +1 -0
- package/dist/hooks/adapters.js +152 -0
- package/dist/hooks/adapters.js.map +1 -0
- package/dist/hooks/handler.d.ts +9 -0
- package/dist/hooks/handler.d.ts.map +1 -0
- package/dist/hooks/handler.js +93 -0
- package/dist/hooks/handler.js.map +1 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +23 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/install.d.ts +16 -0
- package/dist/hooks/install.d.ts.map +1 -0
- package/dist/hooks/install.js +247 -0
- package/dist/hooks/install.js.map +1 -0
- package/dist/hooks/session.d.ts +10 -0
- package/dist/hooks/session.d.ts.map +1 -0
- package/dist/hooks/session.js +72 -0
- package/dist/hooks/session.js.map +1 -0
- package/dist/hooks/stdin.d.ts +6 -0
- package/dist/hooks/stdin.d.ts.map +1 -0
- package/dist/hooks/stdin.js +38 -0
- package/dist/hooks/stdin.js.map +1 -0
- package/dist/hooks/types.d.ts +26 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +7 -0
- package/dist/hooks/types.js.map +1 -0
- 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 +14 -0
- package/dist/reports/html.d.ts.map +1 -0
- package/dist/reports/html.js +353 -0
- package/dist/reports/html.js.map +1 -0
- package/dist/reports/pdf.d.ts +14 -0
- package/dist/reports/pdf.d.ts.map +1 -0
- package/dist/reports/pdf.js +177 -0
- package/dist/reports/pdf.js.map +1 -0
- 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 +156 -2
- package/dist/server/api.js.map +1 -1
- package/dist/server/mcp.d.ts.map +1 -1
- package/dist/server/mcp.js +31 -10
- package/dist/server/mcp.js.map +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-setup.md +131 -0
- package/docs/skills/clawck-usage.md +148 -0
- package/docs/snippets/claude-md.txt +33 -0
- package/docs/snippets/hooks-claude.json +24 -0
- package/docs/snippets/hooks-cline.txt +10 -0
- package/docs/snippets/hooks-codex.json +16 -0
- package/docs/snippets/hooks-cursor.json +16 -0
- package/docs/snippets/hooks-gemini.json +16 -0
- package/docs/snippets/hooks-windsurf.json +16 -0
- package/docs/snippets/mcp-config.json +7 -0
- package/docs/snippets/openclaw-agent-md.txt +18 -0
- package/docs/snippets/openclaw-heartbeat-md.txt +17 -0
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -11,6 +11,39 @@
|
|
|
11
11
|
* clawck status Show running tasks and stats
|
|
12
12
|
* clawck report [--days N] Show timesheet summary
|
|
13
13
|
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
14
47
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
48
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
49
|
};
|
|
@@ -22,22 +55,33 @@ const api_1 = require("../server/api");
|
|
|
22
55
|
const mcp_1 = require("../server/mcp");
|
|
23
56
|
const clawck_1 = require("../core/clawck");
|
|
24
57
|
const types_1 = require("../core/types");
|
|
58
|
+
const pdf_1 = require("../reports/pdf");
|
|
59
|
+
const html_1 = require("../reports/html");
|
|
60
|
+
const periods_1 = require("../reports/periods");
|
|
61
|
+
const patterns_1 = require("../core/patterns");
|
|
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");
|
|
25
66
|
const program = new commander_1.Command();
|
|
26
67
|
program
|
|
27
68
|
.name('clawck')
|
|
28
69
|
.description('⏱️🦀 Clawck — Time tracking for AI agents')
|
|
29
|
-
.version('0.
|
|
30
|
-
.
|
|
70
|
+
.version('0.4.0')
|
|
71
|
+
.enablePositionalOptions()
|
|
72
|
+
.option('--json', 'Output as JSON (for scripting/pipelines)')
|
|
73
|
+
.option('-d, --dir <path>', 'Data directory (also: CLAWCK_DIR env var)');
|
|
31
74
|
// ─── Init ─────────────────────────────────────────────────
|
|
32
75
|
program
|
|
33
76
|
.command('init')
|
|
34
77
|
.description('Initialize a .clawck/ directory in the current folder')
|
|
35
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
78
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
36
79
|
.action(async (opts) => {
|
|
37
|
-
const
|
|
80
|
+
const resolvedDir = resolveDataDir(opts);
|
|
81
|
+
const dir = path_1.default.resolve(resolvedDir);
|
|
38
82
|
// Prevent nesting: don't create .clawck inside an existing .clawck
|
|
39
83
|
const cwd = process.cwd();
|
|
40
|
-
if (path_1.default.basename(cwd) === '.clawck' &&
|
|
84
|
+
if (path_1.default.basename(cwd) === '.clawck' && resolvedDir === '.clawck') {
|
|
41
85
|
console.error(' Already inside a .clawck directory. Aborting to prevent nesting.');
|
|
42
86
|
process.exit(1);
|
|
43
87
|
}
|
|
@@ -59,6 +103,8 @@ program
|
|
|
59
103
|
default_source: 'clawck',
|
|
60
104
|
human_equivalents: types_1.DEFAULT_HUMAN_EQUIVALENTS,
|
|
61
105
|
remote_sources: [],
|
|
106
|
+
patterns: patterns_1.DEFAULT_PATTERNS,
|
|
107
|
+
default_pattern: 'default',
|
|
62
108
|
};
|
|
63
109
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
64
110
|
}
|
|
@@ -78,9 +124,9 @@ program
|
|
|
78
124
|
.command('serve')
|
|
79
125
|
.description('Start the Clawck API server and dashboard')
|
|
80
126
|
.option('-p, --port <number>', 'Port number', '3456')
|
|
81
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
127
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
82
128
|
.action(async (opts) => {
|
|
83
|
-
const config = loadConfig(opts
|
|
129
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
84
130
|
config.port = parseInt(opts.port) || config.port;
|
|
85
131
|
(0, api_1.startServer)(config);
|
|
86
132
|
});
|
|
@@ -88,18 +134,18 @@ program
|
|
|
88
134
|
program
|
|
89
135
|
.command('mcp')
|
|
90
136
|
.description('Start the Clawck MCP server (stdio, for Claude Code / Cline / Cursor)')
|
|
91
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
137
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
92
138
|
.action(async (opts) => {
|
|
93
|
-
const config = loadConfig(opts
|
|
94
|
-
(0, mcp_1.startMCPServer)(config);
|
|
139
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
140
|
+
await (0, mcp_1.startMCPServer)(config);
|
|
95
141
|
});
|
|
96
142
|
// ─── Status ───────────────────────────────────────────────
|
|
97
143
|
program
|
|
98
144
|
.command('status')
|
|
99
145
|
.description('Show currently running tasks and stats')
|
|
100
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
146
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
101
147
|
.action(async (opts) => {
|
|
102
|
-
const config = loadConfig(opts
|
|
148
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
103
149
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
104
150
|
const stats = clawck.stats();
|
|
105
151
|
const running = clawck.running();
|
|
@@ -126,73 +172,236 @@ program
|
|
|
126
172
|
clawck.close();
|
|
127
173
|
});
|
|
128
174
|
// ─── Report ───────────────────────────────────────────────
|
|
129
|
-
program
|
|
175
|
+
const reportCmd = program
|
|
130
176
|
.command('report')
|
|
131
177
|
.description('Show a timesheet summary')
|
|
132
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
133
|
-
.option('--days <number>', 'Number of days to include'
|
|
178
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
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')
|
|
134
184
|
.option('--client <name>', 'Filter by client')
|
|
135
185
|
.option('--project <name>', 'Filter by project')
|
|
136
186
|
.option('--agent <name>', 'Filter by agent')
|
|
187
|
+
.option('--format <type>', 'Output format (terminal, pdf, or html)', 'terminal')
|
|
188
|
+
.option('--output <path>', 'Output file path (for pdf/html format)')
|
|
137
189
|
.option('--detailed', 'Show individual entries')
|
|
190
|
+
.option('--save', 'Save report to database')
|
|
191
|
+
.option('--name <name>', 'Name for saved report')
|
|
138
192
|
.action(async (opts) => {
|
|
139
|
-
const config = loadConfig(opts
|
|
193
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
140
194
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
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;
|
|
144
205
|
const ts = clawck.timesheet(from, to, {
|
|
145
206
|
client: opts.client,
|
|
146
207
|
project: opts.project,
|
|
147
208
|
agent: opts.agent,
|
|
148
209
|
});
|
|
210
|
+
const periodLabel = opts.period || (opts.days ? `${opts.days} days` : period);
|
|
211
|
+
let reportContent = '';
|
|
212
|
+
if (opts.format === 'html') {
|
|
213
|
+
const today = new Date().toISOString().split('T')[0];
|
|
214
|
+
const outputPath = opts.output || `clawck-report-${today}.html`;
|
|
215
|
+
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
216
|
+
const rawEntries = clawck.query({ from, to, client: opts.client, project: opts.project, agent: opts.agent, limit: 10000 });
|
|
217
|
+
const html = (0, html_1.generateTimesheetHTML)(ts, { dateRange, clientName: opts.client, rawEntries, style });
|
|
218
|
+
reportContent = html;
|
|
219
|
+
fs_1.default.writeFileSync(outputPath, html);
|
|
220
|
+
console.log(` HTML report saved to: ${outputPath}`);
|
|
221
|
+
}
|
|
222
|
+
else if (opts.format === 'pdf') {
|
|
223
|
+
const today = new Date().toISOString().split('T')[0];
|
|
224
|
+
const outputPath = opts.output || `clawck-report-${today}.pdf`;
|
|
225
|
+
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
226
|
+
await (0, pdf_1.generateTimesheetPDF)(ts, {
|
|
227
|
+
clientName: opts.client,
|
|
228
|
+
dateRange,
|
|
229
|
+
outputPath,
|
|
230
|
+
style,
|
|
231
|
+
});
|
|
232
|
+
reportContent = fs_1.default.readFileSync(outputPath).toString('base64');
|
|
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);
|
|
149
324
|
if (program.opts().json) {
|
|
150
|
-
console.log(JSON.stringify(
|
|
325
|
+
console.log(JSON.stringify(reports));
|
|
151
326
|
clawck.close();
|
|
152
327
|
return;
|
|
153
328
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
console.log(` 💰 Agent cost: $${ts.total_cost_usd.toFixed(2)}`);
|
|
159
|
-
console.log(` 💚 Est. savings: $${ts.total_savings_usd.toFixed(0)}`);
|
|
160
|
-
console.log(` 🔢 Total entries: ${ts.total_entries}`);
|
|
161
|
-
console.log(` 🪙 Total tokens: ${ts.total_tokens.toLocaleString()}`);
|
|
162
|
-
if (ts.by_project.length > 0) {
|
|
163
|
-
console.log(`\n 📁 By Project:`);
|
|
164
|
-
for (const p of ts.by_project) {
|
|
165
|
-
const bar = '█'.repeat(Math.max(1, Math.round(p.agent_hours / (ts.total_agent_hours || 1) * 20)));
|
|
166
|
-
console.log(` ${bar} ${p.project} (${p.client}): ${p.agent_hours.toFixed(2)}h → ${p.human_equiv_hours.toFixed(2)}h human equiv`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (ts.by_agent.length > 0) {
|
|
170
|
-
console.log(`\n 🤖 By Agent:`);
|
|
171
|
-
for (const a of ts.by_agent) {
|
|
172
|
-
console.log(` • ${a.agent} (${a.model}): ${a.agent_hours.toFixed(2)}h, ${a.success_rate}% success`);
|
|
173
|
-
}
|
|
329
|
+
if (reports.length === 0) {
|
|
330
|
+
console.log('\n No saved reports.\n');
|
|
331
|
+
clawck.close();
|
|
332
|
+
return;
|
|
174
333
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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]}`);
|
|
179
339
|
}
|
|
180
340
|
console.log('');
|
|
181
341
|
clawck.close();
|
|
182
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);
|
|
356
|
+
}
|
|
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.`);
|
|
388
|
+
}
|
|
389
|
+
clawck.close();
|
|
390
|
+
});
|
|
183
391
|
// ─── Start ───────────────────────────────────────────────
|
|
184
392
|
program
|
|
185
393
|
.command('start <task>')
|
|
186
394
|
.description('Start tracking time for a task')
|
|
187
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
395
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
188
396
|
.option('--project <name>', 'Project name')
|
|
189
397
|
.option('--client <name>', 'Client name')
|
|
190
398
|
.option('--category <type>', 'Task category')
|
|
191
399
|
.option('--agent <name>', 'Agent name')
|
|
192
400
|
.option('--model <name>', 'Model name')
|
|
193
401
|
.option('--tags <tags...>', 'Tags')
|
|
402
|
+
.option('--pattern <name>', 'Use a tracking pattern')
|
|
194
403
|
.action(async (task, opts) => {
|
|
195
|
-
const config = loadConfig(opts
|
|
404
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
196
405
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
197
406
|
const entry = clawck.start({
|
|
198
407
|
task,
|
|
@@ -202,6 +411,7 @@ program
|
|
|
202
411
|
agent: opts.agent,
|
|
203
412
|
model: opts.model,
|
|
204
413
|
tags: opts.tags,
|
|
414
|
+
pattern: opts.pattern,
|
|
205
415
|
});
|
|
206
416
|
if (program.opts().json) {
|
|
207
417
|
console.log(JSON.stringify(entry));
|
|
@@ -217,7 +427,7 @@ program
|
|
|
217
427
|
program
|
|
218
428
|
.command('stop <id>')
|
|
219
429
|
.description('Stop tracking time for a task')
|
|
220
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
430
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
221
431
|
.option('--status <status>', 'Outcome (completed/failed)', 'completed')
|
|
222
432
|
.option('--summary <text>', 'Summary of work done')
|
|
223
433
|
.option('--tokens-in <n>', 'Input tokens consumed', parseFloat)
|
|
@@ -225,7 +435,7 @@ program
|
|
|
225
435
|
.option('--cost <n>', 'Cost in USD', parseFloat)
|
|
226
436
|
.option('--tool-calls <n>', 'Number of tool calls', parseInt)
|
|
227
437
|
.action(async (id, opts) => {
|
|
228
|
-
const config = loadConfig(opts
|
|
438
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
229
439
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
230
440
|
const entry = clawck.stop({
|
|
231
441
|
id,
|
|
@@ -247,14 +457,17 @@ program
|
|
|
247
457
|
process.exit(1);
|
|
248
458
|
}
|
|
249
459
|
const duration_minutes = entry.end
|
|
250
|
-
?
|
|
460
|
+
? (new Date(entry.end).getTime() - new Date(entry.start).getTime()) / 60000
|
|
251
461
|
: null;
|
|
252
462
|
if (program.opts().json) {
|
|
253
|
-
console.log(JSON.stringify({ ...entry, duration_minutes }));
|
|
463
|
+
console.log(JSON.stringify({ ...entry, duration_minutes: duration_minutes ? Math.round(duration_minutes * 100) / 100 : null }));
|
|
254
464
|
}
|
|
255
465
|
else {
|
|
256
466
|
console.log(` Stopped: ${entry.task}`);
|
|
257
|
-
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
|
+
}
|
|
258
471
|
}
|
|
259
472
|
clawck.close();
|
|
260
473
|
});
|
|
@@ -262,7 +475,7 @@ program
|
|
|
262
475
|
program
|
|
263
476
|
.command('log <task>')
|
|
264
477
|
.description('Log a completed task retroactively')
|
|
265
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
478
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
266
479
|
.option('--duration <minutes>', 'Duration in minutes', parseFloat)
|
|
267
480
|
.option('--project <name>', 'Project name')
|
|
268
481
|
.option('--client <name>', 'Client name')
|
|
@@ -271,12 +484,13 @@ program
|
|
|
271
484
|
.option('--model <name>', 'Model name')
|
|
272
485
|
.option('--summary <text>', 'Summary of work done')
|
|
273
486
|
.option('--tags <tags...>', 'Tags')
|
|
487
|
+
.option('--pattern <name>', 'Use a tracking pattern')
|
|
274
488
|
.action(async (task, opts) => {
|
|
275
489
|
if (!opts.duration) {
|
|
276
490
|
console.error(' --duration <minutes> is required');
|
|
277
491
|
process.exit(1);
|
|
278
492
|
}
|
|
279
|
-
const config = loadConfig(opts
|
|
493
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
280
494
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
281
495
|
const entry = clawck.log({
|
|
282
496
|
task,
|
|
@@ -288,6 +502,7 @@ program
|
|
|
288
502
|
model: opts.model,
|
|
289
503
|
summary: opts.summary,
|
|
290
504
|
tags: opts.tags,
|
|
505
|
+
pattern: opts.pattern,
|
|
291
506
|
});
|
|
292
507
|
if (program.opts().json) {
|
|
293
508
|
console.log(JSON.stringify(entry));
|
|
@@ -302,9 +517,9 @@ program
|
|
|
302
517
|
program
|
|
303
518
|
.command('get <id>')
|
|
304
519
|
.description('Get a single time entry by ID')
|
|
305
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
520
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
306
521
|
.action(async (id, opts) => {
|
|
307
|
-
const config = loadConfig(opts
|
|
522
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
308
523
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
309
524
|
const entry = clawck.get(id);
|
|
310
525
|
if (!entry) {
|
|
@@ -326,6 +541,8 @@ program
|
|
|
326
541
|
console.log(` Project: ${entry.project} Client: ${entry.client}`);
|
|
327
542
|
console.log(` Agent: ${entry.agent} Model: ${entry.model}`);
|
|
328
543
|
console.log(` Start: ${entry.start} End: ${entry.end || '(running)'}`);
|
|
544
|
+
console.log(` Approved: ${entry.approved ? 'yes' : 'no'}`);
|
|
545
|
+
console.log(` Created: ${entry.created_at} Updated: ${entry.updated_at}`);
|
|
329
546
|
}
|
|
330
547
|
clawck.close();
|
|
331
548
|
});
|
|
@@ -333,7 +550,7 @@ program
|
|
|
333
550
|
program
|
|
334
551
|
.command('entries')
|
|
335
552
|
.description('Query time entries')
|
|
336
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
553
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
337
554
|
.option('--client <name>', 'Filter by client')
|
|
338
555
|
.option('--project <name>', 'Filter by project')
|
|
339
556
|
.option('--agent <name>', 'Filter by agent')
|
|
@@ -341,9 +558,12 @@ program
|
|
|
341
558
|
.option('--from <date>', 'Start date (ISO 8601)')
|
|
342
559
|
.option('--to <date>', 'End date (ISO 8601)')
|
|
343
560
|
.option('--limit <n>', 'Max entries', '50')
|
|
561
|
+
.option('--approved', 'Show only approved entries')
|
|
562
|
+
.option('--unapproved', 'Show only unapproved entries')
|
|
344
563
|
.action(async (opts) => {
|
|
345
|
-
const config = loadConfig(opts
|
|
564
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
346
565
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
566
|
+
const approvedFilter = opts.approved ? true : opts.unapproved ? false : undefined;
|
|
347
567
|
const entries = clawck.query({
|
|
348
568
|
client: opts.client,
|
|
349
569
|
project: opts.project,
|
|
@@ -352,6 +572,7 @@ program
|
|
|
352
572
|
from: opts.from,
|
|
353
573
|
to: opts.to,
|
|
354
574
|
limit: parseInt(opts.limit) || 50,
|
|
575
|
+
approved: approvedFilter,
|
|
355
576
|
});
|
|
356
577
|
if (program.opts().json) {
|
|
357
578
|
console.log(JSON.stringify(entries));
|
|
@@ -368,21 +589,195 @@ program
|
|
|
368
589
|
console.log('');
|
|
369
590
|
clawck.close();
|
|
370
591
|
});
|
|
592
|
+
// ─── Approve ─────────────────────────────────────────────
|
|
593
|
+
program
|
|
594
|
+
.command('approve <id>')
|
|
595
|
+
.description('Approve a time entry (supports 8-char prefix)')
|
|
596
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
597
|
+
.action(async (id, opts) => {
|
|
598
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
599
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
600
|
+
const entry = resolveEntryId(clawck, id);
|
|
601
|
+
if (!entry) {
|
|
602
|
+
clawck.close();
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
const approved = clawck.approve(entry.id);
|
|
606
|
+
if (program.opts().json) {
|
|
607
|
+
console.log(JSON.stringify(approved));
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
console.log(` Approved: ${approved.task}`);
|
|
611
|
+
console.log(` ID: ${approved.id.slice(0, 8)} Project: ${approved.project}`);
|
|
612
|
+
}
|
|
613
|
+
clawck.close();
|
|
614
|
+
});
|
|
615
|
+
// ─── Pattern ─────────────────────────────────────────────
|
|
616
|
+
const pattern = program
|
|
617
|
+
.command('pattern')
|
|
618
|
+
.description('Manage tracking patterns (task templates)');
|
|
619
|
+
pattern
|
|
620
|
+
.command('list')
|
|
621
|
+
.description('List all tracking patterns')
|
|
622
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
623
|
+
.action(async (opts) => {
|
|
624
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
625
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
626
|
+
const patterns = clawck.getPatterns();
|
|
627
|
+
if (program.opts().json) {
|
|
628
|
+
console.log(JSON.stringify(patterns));
|
|
629
|
+
clawck.close();
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
console.log(`\n Tracking Patterns:`);
|
|
633
|
+
for (const p of patterns) {
|
|
634
|
+
const defaultMark = config.default_pattern === p.name ? ' (default)' : '';
|
|
635
|
+
console.log(` - ${p.name}${defaultMark}`);
|
|
636
|
+
if (p.description)
|
|
637
|
+
console.log(` ${p.description}`);
|
|
638
|
+
const fields = [];
|
|
639
|
+
if (p.category)
|
|
640
|
+
fields.push(`category: ${p.category}`);
|
|
641
|
+
if (p.project)
|
|
642
|
+
fields.push(`project: ${p.project}`);
|
|
643
|
+
if (p.client)
|
|
644
|
+
fields.push(`client: ${p.client}`);
|
|
645
|
+
if (p.agent)
|
|
646
|
+
fields.push(`agent: ${p.agent}`);
|
|
647
|
+
if (p.tags?.length)
|
|
648
|
+
fields.push(`tags: ${p.tags.join(', ')}`);
|
|
649
|
+
if (fields.length)
|
|
650
|
+
console.log(` ${fields.join(' ')}`);
|
|
651
|
+
}
|
|
652
|
+
console.log('');
|
|
653
|
+
clawck.close();
|
|
654
|
+
});
|
|
655
|
+
pattern
|
|
656
|
+
.command('add')
|
|
657
|
+
.description('Add a new tracking pattern')
|
|
658
|
+
.requiredOption('--name <name>', 'Pattern name')
|
|
659
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
660
|
+
.option('--category <type>', 'Default category')
|
|
661
|
+
.option('--project <name>', 'Default project')
|
|
662
|
+
.option('--client <name>', 'Default client')
|
|
663
|
+
.option('--agent <name>', 'Default agent')
|
|
664
|
+
.option('--tags <tags...>', 'Default tags')
|
|
665
|
+
.option('--description <text>', 'Pattern description')
|
|
666
|
+
.action(async (opts) => {
|
|
667
|
+
const dataDir = resolveDataDir(opts);
|
|
668
|
+
const config = loadConfig(dataDir);
|
|
669
|
+
const configPath = path_1.default.join(path_1.default.resolve(dataDir), 'config.json');
|
|
670
|
+
const newPattern = { name: opts.name };
|
|
671
|
+
if (opts.description)
|
|
672
|
+
newPattern.description = opts.description;
|
|
673
|
+
if (opts.category)
|
|
674
|
+
newPattern.category = opts.category;
|
|
675
|
+
if (opts.project)
|
|
676
|
+
newPattern.project = opts.project;
|
|
677
|
+
if (opts.client)
|
|
678
|
+
newPattern.client = opts.client;
|
|
679
|
+
if (opts.agent)
|
|
680
|
+
newPattern.agent = opts.agent;
|
|
681
|
+
if (opts.tags)
|
|
682
|
+
newPattern.tags = opts.tags;
|
|
683
|
+
let fileConfig = {};
|
|
684
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
685
|
+
try {
|
|
686
|
+
fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
687
|
+
}
|
|
688
|
+
catch { }
|
|
689
|
+
}
|
|
690
|
+
if (!fileConfig.patterns)
|
|
691
|
+
fileConfig.patterns = [...patterns_1.DEFAULT_PATTERNS];
|
|
692
|
+
fileConfig.patterns.push(newPattern);
|
|
693
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(fileConfig, null, 2));
|
|
694
|
+
console.log(` Added pattern: ${opts.name}`);
|
|
695
|
+
});
|
|
696
|
+
pattern
|
|
697
|
+
.command('use <name>')
|
|
698
|
+
.description('Set the default tracking pattern')
|
|
699
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
700
|
+
.action(async (name, opts) => {
|
|
701
|
+
const dataDir = resolveDataDir(opts);
|
|
702
|
+
const configPath = path_1.default.join(path_1.default.resolve(dataDir), 'config.json');
|
|
703
|
+
let fileConfig = {};
|
|
704
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
705
|
+
try {
|
|
706
|
+
fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
707
|
+
}
|
|
708
|
+
catch { }
|
|
709
|
+
}
|
|
710
|
+
fileConfig.default_pattern = name;
|
|
711
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(fileConfig, null, 2));
|
|
712
|
+
console.log(` Default pattern set to: ${name}`);
|
|
713
|
+
});
|
|
714
|
+
// ─── Setup ───────────────────────────────────────────────
|
|
715
|
+
program
|
|
716
|
+
.command('setup [target]')
|
|
717
|
+
.description('Output ready-to-paste config snippets for agent integration')
|
|
718
|
+
.action(async (target) => {
|
|
719
|
+
const snippetsDir = path_1.default.resolve(__dirname, '../../docs/snippets');
|
|
720
|
+
function readSnippet(filename) {
|
|
721
|
+
const filePath = path_1.default.join(snippetsDir, filename);
|
|
722
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
723
|
+
console.error(` Snippet not found: ${filePath}`);
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
return fs_1.default.readFileSync(filePath, 'utf-8');
|
|
727
|
+
}
|
|
728
|
+
if (!target) {
|
|
729
|
+
console.log(`
|
|
730
|
+
⏱️🦀 Clawck Setup — Agent Integration
|
|
731
|
+
|
|
732
|
+
Available targets:
|
|
733
|
+
|
|
734
|
+
clawck setup claude Output CLAUDE.md time tracking snippet
|
|
735
|
+
clawck setup mcp Output MCP server config JSON
|
|
736
|
+
clawck setup openclaw Output OpenClaw snippets (AGENT.md + HEARTBEAT.md)
|
|
737
|
+
|
|
738
|
+
Paste the output into the appropriate file for your agent platform.
|
|
739
|
+
See docs/skills/clawck-setup.md for full integration guide.
|
|
740
|
+
`);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
switch (target) {
|
|
744
|
+
case 'claude': {
|
|
745
|
+
console.log('\n# Add this to your CLAUDE.md (project or ~/.claude/CLAUDE.md for global):\n');
|
|
746
|
+
console.log(readSnippet('claude-md.txt'));
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
case 'mcp': {
|
|
750
|
+
console.log('\n# Add this to your mcp_servers.json (or ~/.claude/mcp_servers.json for global):\n');
|
|
751
|
+
console.log(readSnippet('mcp-config.json'));
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case 'openclaw': {
|
|
755
|
+
console.log('\n# ─── AGENT.md ─────────────────────────────────────────\n');
|
|
756
|
+
console.log(readSnippet('openclaw-agent-md.txt'));
|
|
757
|
+
console.log('\n# ─── HEARTBEAT.md ─────────────────────────────────────\n');
|
|
758
|
+
console.log(readSnippet('openclaw-heartbeat-md.txt'));
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
default:
|
|
762
|
+
console.error(` Unknown target: "${target}". Run "clawck setup" to see options.`);
|
|
763
|
+
process.exit(1);
|
|
764
|
+
}
|
|
765
|
+
});
|
|
371
766
|
// ─── Seed (for testing) ──────────────────────────────────
|
|
372
767
|
program
|
|
373
768
|
.command('seed')
|
|
374
769
|
.description('Seed the database with sample entries (for testing)')
|
|
375
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
770
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
376
771
|
.option('-n, --count <number>', 'Number of entries', '25')
|
|
377
772
|
.action(async (opts) => {
|
|
378
|
-
const config = loadConfig(opts
|
|
773
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
379
774
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
380
775
|
const count = parseInt(opts.count) || 25;
|
|
381
|
-
const agents = ['
|
|
776
|
+
const agents = ['research-agent-01', 'writer-agent-02', 'coder-agent-03', 'analyst-agent-04', 'outreach-agent-05'];
|
|
382
777
|
const models = ['claude-sonnet-4-20250514', 'claude-haiku-4-5-20251001', 'gpt-4o', 'gemini-2.0-flash'];
|
|
383
|
-
const clients = ['acme-corp', 'globex-inc', 'initech', 'hooli'];
|
|
384
|
-
const projects = ['website-rebuild', 'seo-content', 'grant-research', 'data-migration', 'email-campaigns'];
|
|
385
|
-
const categories = ['research', 'content', 'code', 'data_entry', 'analysis', 'communication', 'testing', 'design'];
|
|
778
|
+
const clients = ['acme-corp', 'globex-inc', 'initech', 'hooli', 'umbrella-co'];
|
|
779
|
+
const projects = ['website-rebuild', 'seo-content', 'grant-research', 'data-migration', 'email-campaigns', 'api-v2'];
|
|
780
|
+
const categories = ['research', 'content', 'code', 'data_entry', 'analysis', 'communication', 'testing', 'design', 'planning', 'other'];
|
|
386
781
|
const tasks = [
|
|
387
782
|
'Research competitor pricing strategies',
|
|
388
783
|
'Write blog post about AI automation',
|
|
@@ -399,20 +794,26 @@ program
|
|
|
399
794
|
'Review and optimize database queries',
|
|
400
795
|
'Create investor pitch deck outline',
|
|
401
796
|
'Summarize recent industry news',
|
|
797
|
+
'Plan sprint roadmap for Q4',
|
|
798
|
+
'Coordinate team standup notes',
|
|
402
799
|
];
|
|
800
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
801
|
+
// Ensure coverage: cycle through all clients, projects, categories, agents
|
|
403
802
|
for (let i = 0; i < count; i++) {
|
|
404
|
-
const daysAgo = Math.random() * 14;
|
|
405
803
|
const durationMin = 5 + Math.random() * 120;
|
|
406
804
|
const tokensIn = Math.round(1000 + Math.random() * 50000);
|
|
407
805
|
const tokensOut = Math.round(500 + Math.random() * 20000);
|
|
408
|
-
const category = categories
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
806
|
+
const category = i < categories.length ? categories[i] : pick(categories);
|
|
807
|
+
const client = i < clients.length ? clients[i] : pick(clients);
|
|
808
|
+
const project = i < projects.length ? projects[i] : pick(projects);
|
|
809
|
+
const agent = i < agents.length ? agents[i] : pick(agents);
|
|
810
|
+
const entry = clawck.log({
|
|
811
|
+
task: pick(tasks),
|
|
812
|
+
project,
|
|
813
|
+
client,
|
|
413
814
|
category,
|
|
414
|
-
agent
|
|
415
|
-
model:
|
|
815
|
+
agent,
|
|
816
|
+
model: pick(models),
|
|
416
817
|
duration_minutes: Math.round(durationMin),
|
|
417
818
|
tokens_in: tokensIn,
|
|
418
819
|
tokens_out: tokensOut,
|
|
@@ -420,27 +821,82 @@ program
|
|
|
420
821
|
summary: 'Auto-generated seed entry for testing',
|
|
421
822
|
tags: ['seed', 'test'],
|
|
422
823
|
});
|
|
824
|
+
// Approve ~60% of completed entries
|
|
825
|
+
if (Math.random() < 0.6) {
|
|
826
|
+
clawck.approve(entry.id);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Add 2-3 running entries
|
|
830
|
+
for (let i = 0; i < 3; i++) {
|
|
831
|
+
clawck.start({
|
|
832
|
+
task: pick(tasks),
|
|
833
|
+
project: pick(projects),
|
|
834
|
+
client: pick(clients),
|
|
835
|
+
category: pick(categories),
|
|
836
|
+
agent: pick(agents),
|
|
837
|
+
model: pick(models),
|
|
838
|
+
tags: ['seed'],
|
|
839
|
+
});
|
|
423
840
|
}
|
|
424
|
-
|
|
425
|
-
|
|
841
|
+
// Add 2-3 failed entries via upsert
|
|
842
|
+
const { v4: uuid } = await Promise.resolve().then(() => __importStar(require('uuid')));
|
|
843
|
+
for (let i = 0; i < 3; i++) {
|
|
844
|
+
const daysAgo = Math.random() * 7;
|
|
845
|
+
const start = new Date(Date.now() - daysAgo * 86400000);
|
|
846
|
+
const end = new Date(start.getTime() + (10 + Math.random() * 30) * 60000);
|
|
847
|
+
clawck.upsert({
|
|
848
|
+
id: uuid(),
|
|
849
|
+
task: pick(tasks),
|
|
850
|
+
project: pick(projects),
|
|
851
|
+
client: pick(clients),
|
|
852
|
+
category: pick(categories),
|
|
853
|
+
agent: pick(agents),
|
|
854
|
+
model: pick(models),
|
|
855
|
+
start: start.toISOString(),
|
|
856
|
+
end: end.toISOString(),
|
|
857
|
+
status: 'failed',
|
|
858
|
+
tokens_in: Math.round(500 + Math.random() * 5000),
|
|
859
|
+
tokens_out: Math.round(100 + Math.random() * 1000),
|
|
860
|
+
cost_usd: Math.round(Math.random() * 0.05 * 10000) / 10000,
|
|
861
|
+
tool_calls: Math.round(Math.random() * 5),
|
|
862
|
+
summary: 'Task failed - auto-generated seed entry',
|
|
863
|
+
tags: ['seed', 'failed'],
|
|
864
|
+
source: 'clawck',
|
|
865
|
+
spec_version: types_1.SPEC_VERSION,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
console.log(`\n ⏱️🦀 Seeded ${count + 6} entries into Clawck!`);
|
|
869
|
+
console.log(` ├─ ${count} completed entries (~60% approved)`);
|
|
870
|
+
console.log(` ├─ 3 running entries`);
|
|
871
|
+
console.log(` ├─ 3 failed entries`);
|
|
872
|
+
console.log(` │`);
|
|
873
|
+
console.log(` │ Try these:`);
|
|
874
|
+
console.log(` │ clawck list -d ${resolveDataDir(opts)}`);
|
|
875
|
+
console.log(` │ clawck report --format html -d ${resolveDataDir(opts)}`);
|
|
876
|
+
console.log(` │ clawck pattern list -d ${resolveDataDir(opts)}`);
|
|
877
|
+
console.log(` │ clawck entries --approved -d ${resolveDataDir(opts)}`);
|
|
878
|
+
console.log(` └─ clawck serve -d ${resolveDataDir(opts)}\n`);
|
|
426
879
|
clawck.close();
|
|
427
880
|
});
|
|
428
881
|
// ─── List ───────────────────────────────────────────────
|
|
429
882
|
program
|
|
430
883
|
.command('list')
|
|
431
884
|
.description('List time entries in a human-readable table')
|
|
432
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
885
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
433
886
|
.option('--days <number>', 'Number of days to include', '7')
|
|
434
887
|
.option('--client <name>', 'Filter by client')
|
|
435
888
|
.option('--project <name>', 'Filter by project')
|
|
436
889
|
.option('--agent <name>', 'Filter by agent')
|
|
437
890
|
.option('--limit <n>', 'Max entries', '50')
|
|
891
|
+
.option('--approved', 'Show only approved entries')
|
|
892
|
+
.option('--unapproved', 'Show only unapproved entries')
|
|
438
893
|
.action(async (opts) => {
|
|
439
|
-
const config = loadConfig(opts
|
|
894
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
440
895
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
441
896
|
const days = parseInt(opts.days) || 7;
|
|
442
897
|
const from = new Date(Date.now() - days * 86400000).toISOString();
|
|
443
898
|
const to = new Date().toISOString();
|
|
899
|
+
const approvedFilter = opts.approved ? true : opts.unapproved ? false : undefined;
|
|
444
900
|
const entries = clawck.query({
|
|
445
901
|
client: opts.client,
|
|
446
902
|
project: opts.project,
|
|
@@ -448,6 +904,7 @@ program
|
|
|
448
904
|
from,
|
|
449
905
|
to,
|
|
450
906
|
limit: parseInt(opts.limit) || 50,
|
|
907
|
+
approved: approvedFilter,
|
|
451
908
|
});
|
|
452
909
|
if (program.opts().json) {
|
|
453
910
|
console.log(JSON.stringify(entries));
|
|
@@ -461,9 +918,9 @@ program
|
|
|
461
918
|
program
|
|
462
919
|
.command('delete <id>')
|
|
463
920
|
.description('Delete a time entry by ID (supports 8-char prefix)')
|
|
464
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
921
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
465
922
|
.action(async (id, opts) => {
|
|
466
|
-
const config = loadConfig(opts
|
|
923
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
467
924
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
468
925
|
const entry = resolveEntryId(clawck, id);
|
|
469
926
|
if (!entry) {
|
|
@@ -484,7 +941,7 @@ program
|
|
|
484
941
|
program
|
|
485
942
|
.command('edit <id>')
|
|
486
943
|
.description('Edit a time entry by ID (supports 8-char prefix)')
|
|
487
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
944
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
488
945
|
.option('--task <text>', 'Update task description')
|
|
489
946
|
.option('--duration <minutes>', 'Update duration (recalculates end)', parseFloat)
|
|
490
947
|
.option('--project <name>', 'Update project')
|
|
@@ -493,7 +950,7 @@ program
|
|
|
493
950
|
.option('--category <type>', 'Update category')
|
|
494
951
|
.option('--summary <text>', 'Update summary')
|
|
495
952
|
.action(async (id, opts) => {
|
|
496
|
-
const config = loadConfig(opts
|
|
953
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
497
954
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
498
955
|
const entry = resolveEntryId(clawck, id);
|
|
499
956
|
if (!entry) {
|
|
@@ -532,15 +989,15 @@ program
|
|
|
532
989
|
// ─── Export ──────────────────────────────────────────────
|
|
533
990
|
program
|
|
534
991
|
.command('export')
|
|
535
|
-
.description('Export time entries as JSON or
|
|
536
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
537
|
-
.option('--format <type>', 'Output format (json or
|
|
992
|
+
.description('Export time entries as JSON, CSV, or ATP envelope')
|
|
993
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
994
|
+
.option('--format <type>', 'Output format (json, csv, or atp)', 'json')
|
|
538
995
|
.option('--days <number>', 'Number of days to include', '7')
|
|
539
996
|
.option('--client <name>', 'Filter by client')
|
|
540
997
|
.option('--project <name>', 'Filter by project')
|
|
541
998
|
.option('--agent <name>', 'Filter by agent')
|
|
542
999
|
.action(async (opts) => {
|
|
543
|
-
const config = loadConfig(opts
|
|
1000
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
544
1001
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
545
1002
|
const days = parseInt(opts.days) || 7;
|
|
546
1003
|
const from = new Date(Date.now() - days * 86400000).toISOString();
|
|
@@ -553,6 +1010,13 @@ program
|
|
|
553
1010
|
to,
|
|
554
1011
|
limit: 10000,
|
|
555
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
|
+
}
|
|
556
1020
|
if (opts.format === 'csv') {
|
|
557
1021
|
const csvEscape = (val) => {
|
|
558
1022
|
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
|
@@ -560,7 +1024,7 @@ program
|
|
|
560
1024
|
}
|
|
561
1025
|
return val;
|
|
562
1026
|
};
|
|
563
|
-
console.log('id,date,task,category,duration_minutes,project,client,agent,model,status,tokens_in,tokens_out,cost_usd,summary');
|
|
1027
|
+
console.log('id,date,task,category,duration_minutes,project,client,agent,model,status,tokens_in,tokens_out,cost_usd,summary,created_at,updated_at');
|
|
564
1028
|
for (const e of entries) {
|
|
565
1029
|
const durationMin = e.end
|
|
566
1030
|
? ((new Date(e.end).getTime() - new Date(e.start).getTime()) / 60000).toFixed(2)
|
|
@@ -581,6 +1045,8 @@ program
|
|
|
581
1045
|
String(e.tokens_out),
|
|
582
1046
|
e.cost_usd.toFixed(4),
|
|
583
1047
|
csvEscape(e.summary),
|
|
1048
|
+
csvEscape(e.created_at || ''),
|
|
1049
|
+
csvEscape(e.updated_at || ''),
|
|
584
1050
|
].join(','));
|
|
585
1051
|
}
|
|
586
1052
|
}
|
|
@@ -589,10 +1055,387 @@ program
|
|
|
589
1055
|
}
|
|
590
1056
|
clawck.close();
|
|
591
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
|
+
});
|
|
1321
|
+
// ─── Hook (runtime, singular) ────────────────────────────
|
|
1322
|
+
const hook = program
|
|
1323
|
+
.command('hook')
|
|
1324
|
+
.description('Runtime hook command (called by platform hooks, reads JSON from stdin)');
|
|
1325
|
+
hook
|
|
1326
|
+
.command('start')
|
|
1327
|
+
.description('Start tracking — called by platform hooks')
|
|
1328
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
1329
|
+
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
1330
|
+
.option('--verbose', 'Log received stdin JSON to stderr for debugging')
|
|
1331
|
+
.action(async (opts) => {
|
|
1332
|
+
try {
|
|
1333
|
+
const raw = await (0, hooks_1.readStdin)();
|
|
1334
|
+
if (opts.verbose)
|
|
1335
|
+
process.stderr.write(`clawck: hook start stdin: ${raw}\n`);
|
|
1336
|
+
let json = {};
|
|
1337
|
+
if (raw.trim()) {
|
|
1338
|
+
try {
|
|
1339
|
+
json = JSON.parse(raw);
|
|
1340
|
+
}
|
|
1341
|
+
catch {
|
|
1342
|
+
json = {};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
const platform = opts.platform || undefined;
|
|
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`);
|
|
1349
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
1350
|
+
await (0, hooks_1.handleHookStart)(config, context);
|
|
1351
|
+
}
|
|
1352
|
+
catch (err) {
|
|
1353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1354
|
+
process.stderr.write(`clawck: hook start failed: ${msg}\n`);
|
|
1355
|
+
}
|
|
1356
|
+
process.exit(0);
|
|
1357
|
+
});
|
|
1358
|
+
hook
|
|
1359
|
+
.command('stop')
|
|
1360
|
+
.description('Stop tracking — called by platform hooks')
|
|
1361
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
1362
|
+
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
1363
|
+
.option('--verbose', 'Log received stdin JSON to stderr for debugging')
|
|
1364
|
+
.action(async (opts) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const raw = await (0, hooks_1.readStdin)();
|
|
1367
|
+
if (opts.verbose)
|
|
1368
|
+
process.stderr.write(`clawck: hook stop stdin: ${raw}\n`);
|
|
1369
|
+
let json = {};
|
|
1370
|
+
if (raw.trim()) {
|
|
1371
|
+
try {
|
|
1372
|
+
json = JSON.parse(raw);
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
json = {};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
const platform = opts.platform || undefined;
|
|
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`);
|
|
1382
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
1383
|
+
await (0, hooks_1.handleHookStop)(config, context);
|
|
1384
|
+
}
|
|
1385
|
+
catch (err) {
|
|
1386
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1387
|
+
process.stderr.write(`clawck: hook stop failed: ${msg}\n`);
|
|
1388
|
+
}
|
|
1389
|
+
process.exit(0);
|
|
1390
|
+
});
|
|
1391
|
+
// ─── Hooks (management, plural) ──────────────────────────
|
|
1392
|
+
const hooks = program
|
|
1393
|
+
.command('hooks')
|
|
1394
|
+
.description('Manage platform hook integrations');
|
|
1395
|
+
hooks
|
|
1396
|
+
.command('install <platform>')
|
|
1397
|
+
.description('Output hook configuration for a platform')
|
|
1398
|
+
.action(async (platformName) => {
|
|
1399
|
+
const key = platformName.toLowerCase();
|
|
1400
|
+
if (!hooks_1.PLATFORMS[key]) {
|
|
1401
|
+
console.error(` Unknown platform: "${platformName}"`);
|
|
1402
|
+
console.error(` Available: ${hooks_1.PLATFORM_NAMES.join(', ')}`);
|
|
1403
|
+
process.exit(1);
|
|
1404
|
+
}
|
|
1405
|
+
const info = hooks_1.PLATFORMS[key];
|
|
1406
|
+
console.log(`\n ⏱️🦀 Clawck Hooks — ${info.displayName}`);
|
|
1407
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
1408
|
+
if (info.configPaths.length > 0) {
|
|
1409
|
+
console.log(`\n Add to: ${info.configPaths[0]}\n`);
|
|
1410
|
+
}
|
|
1411
|
+
console.log(info.generate());
|
|
1412
|
+
console.log(`\n Or copy from: docs/snippets/${info.snippetFile}`);
|
|
1413
|
+
console.log('');
|
|
1414
|
+
});
|
|
1415
|
+
hooks
|
|
1416
|
+
.command('status')
|
|
1417
|
+
.description('Show which platforms have clawck hooks installed')
|
|
1418
|
+
.action(async () => {
|
|
1419
|
+
console.log(`\n ⏱️🦀 Clawck Hooks — Status`);
|
|
1420
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
1421
|
+
for (const name of hooks_1.PLATFORM_NAMES) {
|
|
1422
|
+
const info = hooks_1.PLATFORMS[name];
|
|
1423
|
+
const installed = info.detect();
|
|
1424
|
+
const icon = installed ? '✓' : '✗';
|
|
1425
|
+
console.log(` ${icon} ${info.displayName.padEnd(16)} ${installed ? 'installed' : 'not found'}`);
|
|
1426
|
+
}
|
|
1427
|
+
console.log(`\n Run "clawck hooks install <platform>" to set up a platform.\n`);
|
|
1428
|
+
});
|
|
592
1429
|
// ─── Helpers ──────────────────────────────────────────────
|
|
1430
|
+
function resolveDataDir(subcommandOpts) {
|
|
1431
|
+
return subcommandOpts.dir
|
|
1432
|
+
?? program.opts().dir
|
|
1433
|
+
?? process.env.CLAWCK_DIR
|
|
1434
|
+
?? '.clawck';
|
|
1435
|
+
}
|
|
593
1436
|
function formatDuration(mins) {
|
|
594
1437
|
if (mins < 1)
|
|
595
|
-
return
|
|
1438
|
+
return `${Math.round(mins * 60)}s`;
|
|
596
1439
|
if (mins < 60)
|
|
597
1440
|
return `${Math.round(mins)} min`;
|
|
598
1441
|
const h = Math.floor(mins / 60);
|
|
@@ -632,7 +1475,7 @@ function printEntryTable(entries) {
|
|
|
632
1475
|
console.log('\n No entries found.\n');
|
|
633
1476
|
return;
|
|
634
1477
|
}
|
|
635
|
-
const header = ` ${'ID'.padEnd(10)} ${'Task'.padEnd(42)} ${'Duration'.padEnd(10)} ${'Project'.padEnd(12)} ${'Agent'.padEnd(12)} ${'Time'}`;
|
|
1478
|
+
const header = ` ${'ID'.padEnd(10)} ${'Task'.padEnd(42)} ${'Duration'.padEnd(10)} ${'Project'.padEnd(12)} ${'Agent'.padEnd(12)} ${'OK'.padEnd(4)} ${'Time'}`;
|
|
636
1479
|
console.log(`\n${header}`);
|
|
637
1480
|
console.log(` ${'─'.repeat(header.length - 2)}`);
|
|
638
1481
|
for (const e of entries) {
|
|
@@ -642,7 +1485,8 @@ function printEntryTable(entries) {
|
|
|
642
1485
|
const dur = e.end ? formatDuration(durationMin) : 'running';
|
|
643
1486
|
const task = e.task.length > 40 ? e.task.slice(0, 37) + '...' : e.task;
|
|
644
1487
|
const time = new Date(e.start).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
645
|
-
|
|
1488
|
+
const approved = e.approved ? 'v' : '-';
|
|
1489
|
+
console.log(` ${e.id.slice(0, 8).padEnd(10)} ${task.padEnd(42)} ${dur.padEnd(10)} ${e.project.slice(0, 12).padEnd(12)} ${e.agent.padEnd(12)} ${approved.padEnd(4)} ${time}`);
|
|
646
1490
|
}
|
|
647
1491
|
console.log(`\n ${entries.length} entries\n`);
|
|
648
1492
|
}
|
|
@@ -682,6 +1526,22 @@ function loadConfig(dir) {
|
|
|
682
1526
|
console.error(' Config error: "remote_sources" must be an array');
|
|
683
1527
|
process.exit(1);
|
|
684
1528
|
}
|
|
1529
|
+
if (fileConfig.webhooks !== undefined) {
|
|
1530
|
+
if (!Array.isArray(fileConfig.webhooks)) {
|
|
1531
|
+
console.error(' Config error: "webhooks" must be an array');
|
|
1532
|
+
process.exit(1);
|
|
1533
|
+
}
|
|
1534
|
+
for (const [i, wh] of fileConfig.webhooks.entries()) {
|
|
1535
|
+
if (!wh.url || typeof wh.url !== 'string') {
|
|
1536
|
+
console.error(` Config error: "webhooks[${i}].url" must be a string`);
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
if (!Array.isArray(wh.events)) {
|
|
1540
|
+
console.error(` Config error: "webhooks[${i}].events" must be an array`);
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
685
1545
|
return {
|
|
686
1546
|
...types_1.DEFAULT_CONFIG,
|
|
687
1547
|
...fileConfig,
|