clawck 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -166
- package/dist/cli/index.js +490 -53
- package/dist/cli/index.js.map +1 -1
- package/dist/core/clawck.d.ts +14 -3
- package/dist/core/clawck.d.ts.map +1 -1
- package/dist/core/clawck.js +41 -14
- package/dist/core/clawck.js.map +1 -1
- package/dist/core/database.d.ts +1 -0
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +26 -7
- 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/types.d.ts +31 -0
- package/dist/core/types.d.ts.map +1 -1
- 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 +138 -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 +90 -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 +239 -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 +23 -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/reports/html.d.ts +13 -0
- package/dist/reports/html.d.ts.map +1 -0
- package/dist/reports/html.js +321 -0
- package/dist/reports/html.js.map +1 -0
- package/dist/reports/pdf.d.ts +13 -0
- package/dist/reports/pdf.d.ts.map +1 -0
- package/dist/reports/pdf.js +169 -0
- package/dist/reports/pdf.js.map +1 -0
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +16 -0
- package/dist/server/api.js.map +1 -1
- package/dist/server/mcp.d.ts.map +1 -1
- package/dist/server/mcp.js +30 -9
- package/dist/server/mcp.js.map +1 -1
- package/docs/skills/clawck-setup.md +131 -0
- package/docs/skills/clawck-usage.md +148 -0
- package/docs/snippets/claude-md.txt +24 -0
- package/docs/snippets/hooks-claude.json +16 -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,29 @@ 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 patterns_1 = require("../core/patterns");
|
|
61
|
+
const hooks_1 = require("../hooks");
|
|
25
62
|
const program = new commander_1.Command();
|
|
26
63
|
program
|
|
27
64
|
.name('clawck')
|
|
28
65
|
.description('⏱️🦀 Clawck — Time tracking for AI agents')
|
|
29
|
-
.version('0.
|
|
30
|
-
.
|
|
66
|
+
.version('0.3.0')
|
|
67
|
+
.enablePositionalOptions()
|
|
68
|
+
.option('--json', 'Output as JSON (for scripting/pipelines)')
|
|
69
|
+
.option('-d, --dir <path>', 'Data directory (also: CLAWCK_DIR env var)');
|
|
31
70
|
// ─── Init ─────────────────────────────────────────────────
|
|
32
71
|
program
|
|
33
72
|
.command('init')
|
|
34
73
|
.description('Initialize a .clawck/ directory in the current folder')
|
|
35
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
74
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
36
75
|
.action(async (opts) => {
|
|
37
|
-
const
|
|
76
|
+
const resolvedDir = resolveDataDir(opts);
|
|
77
|
+
const dir = path_1.default.resolve(resolvedDir);
|
|
38
78
|
// Prevent nesting: don't create .clawck inside an existing .clawck
|
|
39
79
|
const cwd = process.cwd();
|
|
40
|
-
if (path_1.default.basename(cwd) === '.clawck' &&
|
|
80
|
+
if (path_1.default.basename(cwd) === '.clawck' && resolvedDir === '.clawck') {
|
|
41
81
|
console.error(' Already inside a .clawck directory. Aborting to prevent nesting.');
|
|
42
82
|
process.exit(1);
|
|
43
83
|
}
|
|
@@ -59,6 +99,8 @@ program
|
|
|
59
99
|
default_source: 'clawck',
|
|
60
100
|
human_equivalents: types_1.DEFAULT_HUMAN_EQUIVALENTS,
|
|
61
101
|
remote_sources: [],
|
|
102
|
+
patterns: patterns_1.DEFAULT_PATTERNS,
|
|
103
|
+
default_pattern: 'default',
|
|
62
104
|
};
|
|
63
105
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
64
106
|
}
|
|
@@ -78,9 +120,9 @@ program
|
|
|
78
120
|
.command('serve')
|
|
79
121
|
.description('Start the Clawck API server and dashboard')
|
|
80
122
|
.option('-p, --port <number>', 'Port number', '3456')
|
|
81
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
123
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
82
124
|
.action(async (opts) => {
|
|
83
|
-
const config = loadConfig(opts
|
|
125
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
84
126
|
config.port = parseInt(opts.port) || config.port;
|
|
85
127
|
(0, api_1.startServer)(config);
|
|
86
128
|
});
|
|
@@ -88,18 +130,18 @@ program
|
|
|
88
130
|
program
|
|
89
131
|
.command('mcp')
|
|
90
132
|
.description('Start the Clawck MCP server (stdio, for Claude Code / Cline / Cursor)')
|
|
91
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
133
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
92
134
|
.action(async (opts) => {
|
|
93
|
-
const config = loadConfig(opts
|
|
135
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
94
136
|
(0, mcp_1.startMCPServer)(config);
|
|
95
137
|
});
|
|
96
138
|
// ─── Status ───────────────────────────────────────────────
|
|
97
139
|
program
|
|
98
140
|
.command('status')
|
|
99
141
|
.description('Show currently running tasks and stats')
|
|
100
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
142
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
101
143
|
.action(async (opts) => {
|
|
102
|
-
const config = loadConfig(opts
|
|
144
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
103
145
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
104
146
|
const stats = clawck.stats();
|
|
105
147
|
const running = clawck.running();
|
|
@@ -129,14 +171,16 @@ program
|
|
|
129
171
|
program
|
|
130
172
|
.command('report')
|
|
131
173
|
.description('Show a timesheet summary')
|
|
132
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
174
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
133
175
|
.option('--days <number>', 'Number of days to include', '7')
|
|
134
176
|
.option('--client <name>', 'Filter by client')
|
|
135
177
|
.option('--project <name>', 'Filter by project')
|
|
136
178
|
.option('--agent <name>', 'Filter by agent')
|
|
179
|
+
.option('--format <type>', 'Output format (terminal, pdf, or html)', 'terminal')
|
|
180
|
+
.option('--output <path>', 'Output file path (for pdf format)')
|
|
137
181
|
.option('--detailed', 'Show individual entries')
|
|
138
182
|
.action(async (opts) => {
|
|
139
|
-
const config = loadConfig(opts
|
|
183
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
140
184
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
141
185
|
const days = parseInt(opts.days) || 7;
|
|
142
186
|
const to = new Date().toISOString();
|
|
@@ -146,6 +190,30 @@ program
|
|
|
146
190
|
project: opts.project,
|
|
147
191
|
agent: opts.agent,
|
|
148
192
|
});
|
|
193
|
+
if (opts.format === 'html') {
|
|
194
|
+
const today = new Date().toISOString().split('T')[0];
|
|
195
|
+
const outputPath = opts.output || `clawck-report-${today}.html`;
|
|
196
|
+
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
197
|
+
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 });
|
|
199
|
+
fs_1.default.writeFileSync(outputPath, html);
|
|
200
|
+
console.log(` HTML report saved to: ${outputPath}`);
|
|
201
|
+
clawck.close();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (opts.format === 'pdf') {
|
|
205
|
+
const today = new Date().toISOString().split('T')[0];
|
|
206
|
+
const outputPath = opts.output || `clawck-report-${today}.pdf`;
|
|
207
|
+
const dateRange = `${from.split('T')[0]} to ${to.split('T')[0]}`;
|
|
208
|
+
await (0, pdf_1.generateTimesheetPDF)(ts, {
|
|
209
|
+
clientName: opts.client,
|
|
210
|
+
dateRange,
|
|
211
|
+
outputPath,
|
|
212
|
+
});
|
|
213
|
+
console.log(` PDF report saved to: ${outputPath}`);
|
|
214
|
+
clawck.close();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
149
217
|
if (program.opts().json) {
|
|
150
218
|
console.log(JSON.stringify(ts));
|
|
151
219
|
clawck.close();
|
|
@@ -184,15 +252,16 @@ program
|
|
|
184
252
|
program
|
|
185
253
|
.command('start <task>')
|
|
186
254
|
.description('Start tracking time for a task')
|
|
187
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
255
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
188
256
|
.option('--project <name>', 'Project name')
|
|
189
257
|
.option('--client <name>', 'Client name')
|
|
190
258
|
.option('--category <type>', 'Task category')
|
|
191
259
|
.option('--agent <name>', 'Agent name')
|
|
192
260
|
.option('--model <name>', 'Model name')
|
|
193
261
|
.option('--tags <tags...>', 'Tags')
|
|
262
|
+
.option('--pattern <name>', 'Use a tracking pattern')
|
|
194
263
|
.action(async (task, opts) => {
|
|
195
|
-
const config = loadConfig(opts
|
|
264
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
196
265
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
197
266
|
const entry = clawck.start({
|
|
198
267
|
task,
|
|
@@ -202,6 +271,7 @@ program
|
|
|
202
271
|
agent: opts.agent,
|
|
203
272
|
model: opts.model,
|
|
204
273
|
tags: opts.tags,
|
|
274
|
+
pattern: opts.pattern,
|
|
205
275
|
});
|
|
206
276
|
if (program.opts().json) {
|
|
207
277
|
console.log(JSON.stringify(entry));
|
|
@@ -217,7 +287,7 @@ program
|
|
|
217
287
|
program
|
|
218
288
|
.command('stop <id>')
|
|
219
289
|
.description('Stop tracking time for a task')
|
|
220
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
290
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
221
291
|
.option('--status <status>', 'Outcome (completed/failed)', 'completed')
|
|
222
292
|
.option('--summary <text>', 'Summary of work done')
|
|
223
293
|
.option('--tokens-in <n>', 'Input tokens consumed', parseFloat)
|
|
@@ -225,7 +295,7 @@ program
|
|
|
225
295
|
.option('--cost <n>', 'Cost in USD', parseFloat)
|
|
226
296
|
.option('--tool-calls <n>', 'Number of tool calls', parseInt)
|
|
227
297
|
.action(async (id, opts) => {
|
|
228
|
-
const config = loadConfig(opts
|
|
298
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
229
299
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
230
300
|
const entry = clawck.stop({
|
|
231
301
|
id,
|
|
@@ -247,14 +317,14 @@ program
|
|
|
247
317
|
process.exit(1);
|
|
248
318
|
}
|
|
249
319
|
const duration_minutes = entry.end
|
|
250
|
-
?
|
|
320
|
+
? (new Date(entry.end).getTime() - new Date(entry.start).getTime()) / 60000
|
|
251
321
|
: null;
|
|
252
322
|
if (program.opts().json) {
|
|
253
|
-
console.log(JSON.stringify({ ...entry, duration_minutes }));
|
|
323
|
+
console.log(JSON.stringify({ ...entry, duration_minutes: duration_minutes ? Math.round(duration_minutes * 100) / 100 : null }));
|
|
254
324
|
}
|
|
255
325
|
else {
|
|
256
326
|
console.log(` Stopped: ${entry.task}`);
|
|
257
|
-
console.log(` Duration: ${duration_minutes}
|
|
327
|
+
console.log(` Duration: ${duration_minutes !== null ? formatDuration(duration_minutes) : 'unknown'} Status: ${entry.status}`);
|
|
258
328
|
}
|
|
259
329
|
clawck.close();
|
|
260
330
|
});
|
|
@@ -262,7 +332,7 @@ program
|
|
|
262
332
|
program
|
|
263
333
|
.command('log <task>')
|
|
264
334
|
.description('Log a completed task retroactively')
|
|
265
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
335
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
266
336
|
.option('--duration <minutes>', 'Duration in minutes', parseFloat)
|
|
267
337
|
.option('--project <name>', 'Project name')
|
|
268
338
|
.option('--client <name>', 'Client name')
|
|
@@ -271,12 +341,13 @@ program
|
|
|
271
341
|
.option('--model <name>', 'Model name')
|
|
272
342
|
.option('--summary <text>', 'Summary of work done')
|
|
273
343
|
.option('--tags <tags...>', 'Tags')
|
|
344
|
+
.option('--pattern <name>', 'Use a tracking pattern')
|
|
274
345
|
.action(async (task, opts) => {
|
|
275
346
|
if (!opts.duration) {
|
|
276
347
|
console.error(' --duration <minutes> is required');
|
|
277
348
|
process.exit(1);
|
|
278
349
|
}
|
|
279
|
-
const config = loadConfig(opts
|
|
350
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
280
351
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
281
352
|
const entry = clawck.log({
|
|
282
353
|
task,
|
|
@@ -288,6 +359,7 @@ program
|
|
|
288
359
|
model: opts.model,
|
|
289
360
|
summary: opts.summary,
|
|
290
361
|
tags: opts.tags,
|
|
362
|
+
pattern: opts.pattern,
|
|
291
363
|
});
|
|
292
364
|
if (program.opts().json) {
|
|
293
365
|
console.log(JSON.stringify(entry));
|
|
@@ -302,9 +374,9 @@ program
|
|
|
302
374
|
program
|
|
303
375
|
.command('get <id>')
|
|
304
376
|
.description('Get a single time entry by ID')
|
|
305
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
377
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
306
378
|
.action(async (id, opts) => {
|
|
307
|
-
const config = loadConfig(opts
|
|
379
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
308
380
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
309
381
|
const entry = clawck.get(id);
|
|
310
382
|
if (!entry) {
|
|
@@ -326,6 +398,8 @@ program
|
|
|
326
398
|
console.log(` Project: ${entry.project} Client: ${entry.client}`);
|
|
327
399
|
console.log(` Agent: ${entry.agent} Model: ${entry.model}`);
|
|
328
400
|
console.log(` Start: ${entry.start} End: ${entry.end || '(running)'}`);
|
|
401
|
+
console.log(` Approved: ${entry.approved ? 'yes' : 'no'}`);
|
|
402
|
+
console.log(` Created: ${entry.created_at} Updated: ${entry.updated_at}`);
|
|
329
403
|
}
|
|
330
404
|
clawck.close();
|
|
331
405
|
});
|
|
@@ -333,7 +407,7 @@ program
|
|
|
333
407
|
program
|
|
334
408
|
.command('entries')
|
|
335
409
|
.description('Query time entries')
|
|
336
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
410
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
337
411
|
.option('--client <name>', 'Filter by client')
|
|
338
412
|
.option('--project <name>', 'Filter by project')
|
|
339
413
|
.option('--agent <name>', 'Filter by agent')
|
|
@@ -341,9 +415,12 @@ program
|
|
|
341
415
|
.option('--from <date>', 'Start date (ISO 8601)')
|
|
342
416
|
.option('--to <date>', 'End date (ISO 8601)')
|
|
343
417
|
.option('--limit <n>', 'Max entries', '50')
|
|
418
|
+
.option('--approved', 'Show only approved entries')
|
|
419
|
+
.option('--unapproved', 'Show only unapproved entries')
|
|
344
420
|
.action(async (opts) => {
|
|
345
|
-
const config = loadConfig(opts
|
|
421
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
346
422
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
423
|
+
const approvedFilter = opts.approved ? true : opts.unapproved ? false : undefined;
|
|
347
424
|
const entries = clawck.query({
|
|
348
425
|
client: opts.client,
|
|
349
426
|
project: opts.project,
|
|
@@ -352,6 +429,7 @@ program
|
|
|
352
429
|
from: opts.from,
|
|
353
430
|
to: opts.to,
|
|
354
431
|
limit: parseInt(opts.limit) || 50,
|
|
432
|
+
approved: approvedFilter,
|
|
355
433
|
});
|
|
356
434
|
if (program.opts().json) {
|
|
357
435
|
console.log(JSON.stringify(entries));
|
|
@@ -368,21 +446,195 @@ program
|
|
|
368
446
|
console.log('');
|
|
369
447
|
clawck.close();
|
|
370
448
|
});
|
|
449
|
+
// ─── Approve ─────────────────────────────────────────────
|
|
450
|
+
program
|
|
451
|
+
.command('approve <id>')
|
|
452
|
+
.description('Approve a time entry (supports 8-char prefix)')
|
|
453
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
454
|
+
.action(async (id, opts) => {
|
|
455
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
456
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
457
|
+
const entry = resolveEntryId(clawck, id);
|
|
458
|
+
if (!entry) {
|
|
459
|
+
clawck.close();
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
const approved = clawck.approve(entry.id);
|
|
463
|
+
if (program.opts().json) {
|
|
464
|
+
console.log(JSON.stringify(approved));
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
console.log(` Approved: ${approved.task}`);
|
|
468
|
+
console.log(` ID: ${approved.id.slice(0, 8)} Project: ${approved.project}`);
|
|
469
|
+
}
|
|
470
|
+
clawck.close();
|
|
471
|
+
});
|
|
472
|
+
// ─── Pattern ─────────────────────────────────────────────
|
|
473
|
+
const pattern = program
|
|
474
|
+
.command('pattern')
|
|
475
|
+
.description('Manage tracking patterns (task templates)');
|
|
476
|
+
pattern
|
|
477
|
+
.command('list')
|
|
478
|
+
.description('List all tracking patterns')
|
|
479
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
480
|
+
.action(async (opts) => {
|
|
481
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
482
|
+
const clawck = await new clawck_1.Clawck(config).ready();
|
|
483
|
+
const patterns = clawck.getPatterns();
|
|
484
|
+
if (program.opts().json) {
|
|
485
|
+
console.log(JSON.stringify(patterns));
|
|
486
|
+
clawck.close();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
console.log(`\n Tracking Patterns:`);
|
|
490
|
+
for (const p of patterns) {
|
|
491
|
+
const defaultMark = config.default_pattern === p.name ? ' (default)' : '';
|
|
492
|
+
console.log(` - ${p.name}${defaultMark}`);
|
|
493
|
+
if (p.description)
|
|
494
|
+
console.log(` ${p.description}`);
|
|
495
|
+
const fields = [];
|
|
496
|
+
if (p.category)
|
|
497
|
+
fields.push(`category: ${p.category}`);
|
|
498
|
+
if (p.project)
|
|
499
|
+
fields.push(`project: ${p.project}`);
|
|
500
|
+
if (p.client)
|
|
501
|
+
fields.push(`client: ${p.client}`);
|
|
502
|
+
if (p.agent)
|
|
503
|
+
fields.push(`agent: ${p.agent}`);
|
|
504
|
+
if (p.tags?.length)
|
|
505
|
+
fields.push(`tags: ${p.tags.join(', ')}`);
|
|
506
|
+
if (fields.length)
|
|
507
|
+
console.log(` ${fields.join(' ')}`);
|
|
508
|
+
}
|
|
509
|
+
console.log('');
|
|
510
|
+
clawck.close();
|
|
511
|
+
});
|
|
512
|
+
pattern
|
|
513
|
+
.command('add')
|
|
514
|
+
.description('Add a new tracking pattern')
|
|
515
|
+
.requiredOption('--name <name>', 'Pattern name')
|
|
516
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
517
|
+
.option('--category <type>', 'Default category')
|
|
518
|
+
.option('--project <name>', 'Default project')
|
|
519
|
+
.option('--client <name>', 'Default client')
|
|
520
|
+
.option('--agent <name>', 'Default agent')
|
|
521
|
+
.option('--tags <tags...>', 'Default tags')
|
|
522
|
+
.option('--description <text>', 'Pattern description')
|
|
523
|
+
.action(async (opts) => {
|
|
524
|
+
const dataDir = resolveDataDir(opts);
|
|
525
|
+
const config = loadConfig(dataDir);
|
|
526
|
+
const configPath = path_1.default.join(path_1.default.resolve(dataDir), 'config.json');
|
|
527
|
+
const newPattern = { name: opts.name };
|
|
528
|
+
if (opts.description)
|
|
529
|
+
newPattern.description = opts.description;
|
|
530
|
+
if (opts.category)
|
|
531
|
+
newPattern.category = opts.category;
|
|
532
|
+
if (opts.project)
|
|
533
|
+
newPattern.project = opts.project;
|
|
534
|
+
if (opts.client)
|
|
535
|
+
newPattern.client = opts.client;
|
|
536
|
+
if (opts.agent)
|
|
537
|
+
newPattern.agent = opts.agent;
|
|
538
|
+
if (opts.tags)
|
|
539
|
+
newPattern.tags = opts.tags;
|
|
540
|
+
let fileConfig = {};
|
|
541
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
542
|
+
try {
|
|
543
|
+
fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
544
|
+
}
|
|
545
|
+
catch { }
|
|
546
|
+
}
|
|
547
|
+
if (!fileConfig.patterns)
|
|
548
|
+
fileConfig.patterns = [...patterns_1.DEFAULT_PATTERNS];
|
|
549
|
+
fileConfig.patterns.push(newPattern);
|
|
550
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(fileConfig, null, 2));
|
|
551
|
+
console.log(` Added pattern: ${opts.name}`);
|
|
552
|
+
});
|
|
553
|
+
pattern
|
|
554
|
+
.command('use <name>')
|
|
555
|
+
.description('Set the default tracking pattern')
|
|
556
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
557
|
+
.action(async (name, opts) => {
|
|
558
|
+
const dataDir = resolveDataDir(opts);
|
|
559
|
+
const configPath = path_1.default.join(path_1.default.resolve(dataDir), 'config.json');
|
|
560
|
+
let fileConfig = {};
|
|
561
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
562
|
+
try {
|
|
563
|
+
fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
564
|
+
}
|
|
565
|
+
catch { }
|
|
566
|
+
}
|
|
567
|
+
fileConfig.default_pattern = name;
|
|
568
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(fileConfig, null, 2));
|
|
569
|
+
console.log(` Default pattern set to: ${name}`);
|
|
570
|
+
});
|
|
571
|
+
// ─── Setup ───────────────────────────────────────────────
|
|
572
|
+
program
|
|
573
|
+
.command('setup [target]')
|
|
574
|
+
.description('Output ready-to-paste config snippets for agent integration')
|
|
575
|
+
.action(async (target) => {
|
|
576
|
+
const snippetsDir = path_1.default.resolve(__dirname, '../../docs/snippets');
|
|
577
|
+
function readSnippet(filename) {
|
|
578
|
+
const filePath = path_1.default.join(snippetsDir, filename);
|
|
579
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
580
|
+
console.error(` Snippet not found: ${filePath}`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
return fs_1.default.readFileSync(filePath, 'utf-8');
|
|
584
|
+
}
|
|
585
|
+
if (!target) {
|
|
586
|
+
console.log(`
|
|
587
|
+
⏱️🦀 Clawck Setup — Agent Integration
|
|
588
|
+
|
|
589
|
+
Available targets:
|
|
590
|
+
|
|
591
|
+
clawck setup claude Output CLAUDE.md time tracking snippet
|
|
592
|
+
clawck setup mcp Output MCP server config JSON
|
|
593
|
+
clawck setup openclaw Output OpenClaw snippets (AGENT.md + HEARTBEAT.md)
|
|
594
|
+
|
|
595
|
+
Paste the output into the appropriate file for your agent platform.
|
|
596
|
+
See docs/skills/clawck-setup.md for full integration guide.
|
|
597
|
+
`);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
switch (target) {
|
|
601
|
+
case 'claude': {
|
|
602
|
+
console.log('\n# Add this to your CLAUDE.md (project or ~/.claude/CLAUDE.md for global):\n');
|
|
603
|
+
console.log(readSnippet('claude-md.txt'));
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
case 'mcp': {
|
|
607
|
+
console.log('\n# Add this to your mcp_servers.json (or ~/.claude/mcp_servers.json for global):\n');
|
|
608
|
+
console.log(readSnippet('mcp-config.json'));
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
case 'openclaw': {
|
|
612
|
+
console.log('\n# ─── AGENT.md ─────────────────────────────────────────\n');
|
|
613
|
+
console.log(readSnippet('openclaw-agent-md.txt'));
|
|
614
|
+
console.log('\n# ─── HEARTBEAT.md ─────────────────────────────────────\n');
|
|
615
|
+
console.log(readSnippet('openclaw-heartbeat-md.txt'));
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
default:
|
|
619
|
+
console.error(` Unknown target: "${target}". Run "clawck setup" to see options.`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
371
623
|
// ─── Seed (for testing) ──────────────────────────────────
|
|
372
624
|
program
|
|
373
625
|
.command('seed')
|
|
374
626
|
.description('Seed the database with sample entries (for testing)')
|
|
375
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
627
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
376
628
|
.option('-n, --count <number>', 'Number of entries', '25')
|
|
377
629
|
.action(async (opts) => {
|
|
378
|
-
const config = loadConfig(opts
|
|
630
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
379
631
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
380
632
|
const count = parseInt(opts.count) || 25;
|
|
381
633
|
const agents = ['cubi-research-01', 'cubi-writer-02', 'cubi-coder-03', 'cubi-analyst-04', 'cubi-outreach-05'];
|
|
382
634
|
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'];
|
|
635
|
+
const clients = ['acme-corp', 'globex-inc', 'initech', 'hooli', 'umbrella-co'];
|
|
636
|
+
const projects = ['website-rebuild', 'seo-content', 'grant-research', 'data-migration', 'email-campaigns', 'api-v2'];
|
|
637
|
+
const categories = ['research', 'content', 'code', 'data_entry', 'analysis', 'communication', 'testing', 'design', 'planning', 'other'];
|
|
386
638
|
const tasks = [
|
|
387
639
|
'Research competitor pricing strategies',
|
|
388
640
|
'Write blog post about AI automation',
|
|
@@ -399,20 +651,26 @@ program
|
|
|
399
651
|
'Review and optimize database queries',
|
|
400
652
|
'Create investor pitch deck outline',
|
|
401
653
|
'Summarize recent industry news',
|
|
654
|
+
'Plan sprint roadmap for Q4',
|
|
655
|
+
'Coordinate team standup notes',
|
|
402
656
|
];
|
|
657
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
658
|
+
// Ensure coverage: cycle through all clients, projects, categories, agents
|
|
403
659
|
for (let i = 0; i < count; i++) {
|
|
404
|
-
const daysAgo = Math.random() * 14;
|
|
405
660
|
const durationMin = 5 + Math.random() * 120;
|
|
406
661
|
const tokensIn = Math.round(1000 + Math.random() * 50000);
|
|
407
662
|
const tokensOut = Math.round(500 + Math.random() * 20000);
|
|
408
|
-
const category = categories
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
663
|
+
const category = i < categories.length ? categories[i] : pick(categories);
|
|
664
|
+
const client = i < clients.length ? clients[i] : pick(clients);
|
|
665
|
+
const project = i < projects.length ? projects[i] : pick(projects);
|
|
666
|
+
const agent = i < agents.length ? agents[i] : pick(agents);
|
|
667
|
+
const entry = clawck.log({
|
|
668
|
+
task: pick(tasks),
|
|
669
|
+
project,
|
|
670
|
+
client,
|
|
413
671
|
category,
|
|
414
|
-
agent
|
|
415
|
-
model:
|
|
672
|
+
agent,
|
|
673
|
+
model: pick(models),
|
|
416
674
|
duration_minutes: Math.round(durationMin),
|
|
417
675
|
tokens_in: tokensIn,
|
|
418
676
|
tokens_out: tokensOut,
|
|
@@ -420,27 +678,82 @@ program
|
|
|
420
678
|
summary: 'Auto-generated seed entry for testing',
|
|
421
679
|
tags: ['seed', 'test'],
|
|
422
680
|
});
|
|
681
|
+
// Approve ~60% of completed entries
|
|
682
|
+
if (Math.random() < 0.6) {
|
|
683
|
+
clawck.approve(entry.id);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// Add 2-3 running entries
|
|
687
|
+
for (let i = 0; i < 3; i++) {
|
|
688
|
+
clawck.start({
|
|
689
|
+
task: pick(tasks),
|
|
690
|
+
project: pick(projects),
|
|
691
|
+
client: pick(clients),
|
|
692
|
+
category: pick(categories),
|
|
693
|
+
agent: pick(agents),
|
|
694
|
+
model: pick(models),
|
|
695
|
+
tags: ['seed'],
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
// Add 2-3 failed entries via upsert
|
|
699
|
+
const { v4: uuid } = await Promise.resolve().then(() => __importStar(require('uuid')));
|
|
700
|
+
for (let i = 0; i < 3; i++) {
|
|
701
|
+
const daysAgo = Math.random() * 7;
|
|
702
|
+
const start = new Date(Date.now() - daysAgo * 86400000);
|
|
703
|
+
const end = new Date(start.getTime() + (10 + Math.random() * 30) * 60000);
|
|
704
|
+
clawck.upsert({
|
|
705
|
+
id: uuid(),
|
|
706
|
+
task: pick(tasks),
|
|
707
|
+
project: pick(projects),
|
|
708
|
+
client: pick(clients),
|
|
709
|
+
category: pick(categories),
|
|
710
|
+
agent: pick(agents),
|
|
711
|
+
model: pick(models),
|
|
712
|
+
start: start.toISOString(),
|
|
713
|
+
end: end.toISOString(),
|
|
714
|
+
status: 'failed',
|
|
715
|
+
tokens_in: Math.round(500 + Math.random() * 5000),
|
|
716
|
+
tokens_out: Math.round(100 + Math.random() * 1000),
|
|
717
|
+
cost_usd: Math.round(Math.random() * 0.05 * 10000) / 10000,
|
|
718
|
+
tool_calls: Math.round(Math.random() * 5),
|
|
719
|
+
summary: 'Task failed - auto-generated seed entry',
|
|
720
|
+
tags: ['seed', 'failed'],
|
|
721
|
+
source: 'clawck',
|
|
722
|
+
spec_version: '0.1.0',
|
|
723
|
+
});
|
|
423
724
|
}
|
|
424
|
-
console.log(`\n ⏱️🦀 Seeded ${count} entries into Clawck!`);
|
|
425
|
-
console.log(`
|
|
725
|
+
console.log(`\n ⏱️🦀 Seeded ${count + 6} entries into Clawck!`);
|
|
726
|
+
console.log(` ├─ ${count} completed entries (~60% approved)`);
|
|
727
|
+
console.log(` ├─ 3 running entries`);
|
|
728
|
+
console.log(` ├─ 3 failed entries`);
|
|
729
|
+
console.log(` │`);
|
|
730
|
+
console.log(` │ Try these:`);
|
|
731
|
+
console.log(` │ clawck list -d ${resolveDataDir(opts)}`);
|
|
732
|
+
console.log(` │ clawck report --format html -d ${resolveDataDir(opts)}`);
|
|
733
|
+
console.log(` │ clawck pattern list -d ${resolveDataDir(opts)}`);
|
|
734
|
+
console.log(` │ clawck entries --approved -d ${resolveDataDir(opts)}`);
|
|
735
|
+
console.log(` └─ clawck serve -d ${resolveDataDir(opts)}\n`);
|
|
426
736
|
clawck.close();
|
|
427
737
|
});
|
|
428
738
|
// ─── List ───────────────────────────────────────────────
|
|
429
739
|
program
|
|
430
740
|
.command('list')
|
|
431
741
|
.description('List time entries in a human-readable table')
|
|
432
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
742
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
433
743
|
.option('--days <number>', 'Number of days to include', '7')
|
|
434
744
|
.option('--client <name>', 'Filter by client')
|
|
435
745
|
.option('--project <name>', 'Filter by project')
|
|
436
746
|
.option('--agent <name>', 'Filter by agent')
|
|
437
747
|
.option('--limit <n>', 'Max entries', '50')
|
|
748
|
+
.option('--approved', 'Show only approved entries')
|
|
749
|
+
.option('--unapproved', 'Show only unapproved entries')
|
|
438
750
|
.action(async (opts) => {
|
|
439
|
-
const config = loadConfig(opts
|
|
751
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
440
752
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
441
753
|
const days = parseInt(opts.days) || 7;
|
|
442
754
|
const from = new Date(Date.now() - days * 86400000).toISOString();
|
|
443
755
|
const to = new Date().toISOString();
|
|
756
|
+
const approvedFilter = opts.approved ? true : opts.unapproved ? false : undefined;
|
|
444
757
|
const entries = clawck.query({
|
|
445
758
|
client: opts.client,
|
|
446
759
|
project: opts.project,
|
|
@@ -448,6 +761,7 @@ program
|
|
|
448
761
|
from,
|
|
449
762
|
to,
|
|
450
763
|
limit: parseInt(opts.limit) || 50,
|
|
764
|
+
approved: approvedFilter,
|
|
451
765
|
});
|
|
452
766
|
if (program.opts().json) {
|
|
453
767
|
console.log(JSON.stringify(entries));
|
|
@@ -461,9 +775,9 @@ program
|
|
|
461
775
|
program
|
|
462
776
|
.command('delete <id>')
|
|
463
777
|
.description('Delete a time entry by ID (supports 8-char prefix)')
|
|
464
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
778
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
465
779
|
.action(async (id, opts) => {
|
|
466
|
-
const config = loadConfig(opts
|
|
780
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
467
781
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
468
782
|
const entry = resolveEntryId(clawck, id);
|
|
469
783
|
if (!entry) {
|
|
@@ -484,7 +798,7 @@ program
|
|
|
484
798
|
program
|
|
485
799
|
.command('edit <id>')
|
|
486
800
|
.description('Edit a time entry by ID (supports 8-char prefix)')
|
|
487
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
801
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
488
802
|
.option('--task <text>', 'Update task description')
|
|
489
803
|
.option('--duration <minutes>', 'Update duration (recalculates end)', parseFloat)
|
|
490
804
|
.option('--project <name>', 'Update project')
|
|
@@ -493,7 +807,7 @@ program
|
|
|
493
807
|
.option('--category <type>', 'Update category')
|
|
494
808
|
.option('--summary <text>', 'Update summary')
|
|
495
809
|
.action(async (id, opts) => {
|
|
496
|
-
const config = loadConfig(opts
|
|
810
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
497
811
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
498
812
|
const entry = resolveEntryId(clawck, id);
|
|
499
813
|
if (!entry) {
|
|
@@ -533,14 +847,14 @@ program
|
|
|
533
847
|
program
|
|
534
848
|
.command('export')
|
|
535
849
|
.description('Export time entries as JSON or CSV')
|
|
536
|
-
.option('-d, --dir <path>', 'Data directory'
|
|
850
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
537
851
|
.option('--format <type>', 'Output format (json or csv)', 'json')
|
|
538
852
|
.option('--days <number>', 'Number of days to include', '7')
|
|
539
853
|
.option('--client <name>', 'Filter by client')
|
|
540
854
|
.option('--project <name>', 'Filter by project')
|
|
541
855
|
.option('--agent <name>', 'Filter by agent')
|
|
542
856
|
.action(async (opts) => {
|
|
543
|
-
const config = loadConfig(opts
|
|
857
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
544
858
|
const clawck = await new clawck_1.Clawck(config).ready();
|
|
545
859
|
const days = parseInt(opts.days) || 7;
|
|
546
860
|
const from = new Date(Date.now() - days * 86400000).toISOString();
|
|
@@ -560,7 +874,7 @@ program
|
|
|
560
874
|
}
|
|
561
875
|
return val;
|
|
562
876
|
};
|
|
563
|
-
console.log('id,date,task,category,duration_minutes,project,client,agent,model,status,tokens_in,tokens_out,cost_usd,summary');
|
|
877
|
+
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
878
|
for (const e of entries) {
|
|
565
879
|
const durationMin = e.end
|
|
566
880
|
? ((new Date(e.end).getTime() - new Date(e.start).getTime()) / 60000).toFixed(2)
|
|
@@ -581,6 +895,8 @@ program
|
|
|
581
895
|
String(e.tokens_out),
|
|
582
896
|
e.cost_usd.toFixed(4),
|
|
583
897
|
csvEscape(e.summary),
|
|
898
|
+
csvEscape(e.created_at || ''),
|
|
899
|
+
csvEscape(e.updated_at || ''),
|
|
584
900
|
].join(','));
|
|
585
901
|
}
|
|
586
902
|
}
|
|
@@ -589,10 +905,114 @@ program
|
|
|
589
905
|
}
|
|
590
906
|
clawck.close();
|
|
591
907
|
});
|
|
908
|
+
// ─── Hook (runtime, singular) ────────────────────────────
|
|
909
|
+
const hook = program
|
|
910
|
+
.command('hook')
|
|
911
|
+
.description('Runtime hook command (called by platform hooks, reads JSON from stdin)');
|
|
912
|
+
hook
|
|
913
|
+
.command('start')
|
|
914
|
+
.description('Start tracking — called by platform hooks')
|
|
915
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
916
|
+
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
917
|
+
.action(async (opts) => {
|
|
918
|
+
try {
|
|
919
|
+
const raw = await (0, hooks_1.readStdin)();
|
|
920
|
+
let json = {};
|
|
921
|
+
if (raw.trim()) {
|
|
922
|
+
try {
|
|
923
|
+
json = JSON.parse(raw);
|
|
924
|
+
}
|
|
925
|
+
catch {
|
|
926
|
+
json = {};
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const platform = opts.platform || undefined;
|
|
930
|
+
const context = (0, hooks_1.normalize)(json, platform);
|
|
931
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
932
|
+
await (0, hooks_1.handleHookStart)(config, context);
|
|
933
|
+
}
|
|
934
|
+
catch (err) {
|
|
935
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
936
|
+
process.stderr.write(`clawck: hook start failed: ${msg}\n`);
|
|
937
|
+
}
|
|
938
|
+
process.exit(0);
|
|
939
|
+
});
|
|
940
|
+
hook
|
|
941
|
+
.command('stop')
|
|
942
|
+
.description('Stop tracking — called by platform hooks')
|
|
943
|
+
.option('-d, --dir <path>', 'Data directory')
|
|
944
|
+
.option('--platform <name>', 'Force platform (claude|gemini|cursor|cline|windsurf|codex)')
|
|
945
|
+
.action(async (opts) => {
|
|
946
|
+
try {
|
|
947
|
+
const raw = await (0, hooks_1.readStdin)();
|
|
948
|
+
let json = {};
|
|
949
|
+
if (raw.trim()) {
|
|
950
|
+
try {
|
|
951
|
+
json = JSON.parse(raw);
|
|
952
|
+
}
|
|
953
|
+
catch {
|
|
954
|
+
json = {};
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const platform = opts.platform || undefined;
|
|
958
|
+
const context = (0, hooks_1.normalize)(json, platform);
|
|
959
|
+
const config = loadConfig(resolveDataDir(opts));
|
|
960
|
+
await (0, hooks_1.handleHookStop)(config, context);
|
|
961
|
+
}
|
|
962
|
+
catch (err) {
|
|
963
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
964
|
+
process.stderr.write(`clawck: hook stop failed: ${msg}\n`);
|
|
965
|
+
}
|
|
966
|
+
process.exit(0);
|
|
967
|
+
});
|
|
968
|
+
// ─── Hooks (management, plural) ──────────────────────────
|
|
969
|
+
const hooks = program
|
|
970
|
+
.command('hooks')
|
|
971
|
+
.description('Manage platform hook integrations');
|
|
972
|
+
hooks
|
|
973
|
+
.command('install <platform>')
|
|
974
|
+
.description('Output hook configuration for a platform')
|
|
975
|
+
.action(async (platformName) => {
|
|
976
|
+
const key = platformName.toLowerCase();
|
|
977
|
+
if (!hooks_1.PLATFORMS[key]) {
|
|
978
|
+
console.error(` Unknown platform: "${platformName}"`);
|
|
979
|
+
console.error(` Available: ${hooks_1.PLATFORM_NAMES.join(', ')}`);
|
|
980
|
+
process.exit(1);
|
|
981
|
+
}
|
|
982
|
+
const info = hooks_1.PLATFORMS[key];
|
|
983
|
+
console.log(`\n ⏱️🦀 Clawck Hooks — ${info.displayName}`);
|
|
984
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
985
|
+
if (info.configPaths.length > 0) {
|
|
986
|
+
console.log(`\n Add to: ${info.configPaths[0]}\n`);
|
|
987
|
+
}
|
|
988
|
+
console.log(info.generate());
|
|
989
|
+
console.log(`\n Or copy from: docs/snippets/${info.snippetFile}`);
|
|
990
|
+
console.log('');
|
|
991
|
+
});
|
|
992
|
+
hooks
|
|
993
|
+
.command('status')
|
|
994
|
+
.description('Show which platforms have clawck hooks installed')
|
|
995
|
+
.action(async () => {
|
|
996
|
+
console.log(`\n ⏱️🦀 Clawck Hooks — Status`);
|
|
997
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
998
|
+
for (const name of hooks_1.PLATFORM_NAMES) {
|
|
999
|
+
const info = hooks_1.PLATFORMS[name];
|
|
1000
|
+
const installed = info.detect();
|
|
1001
|
+
const icon = installed ? '✓' : '✗';
|
|
1002
|
+
console.log(` ${icon} ${info.displayName.padEnd(16)} ${installed ? 'installed' : 'not found'}`);
|
|
1003
|
+
}
|
|
1004
|
+
console.log(`\n Run "clawck hooks install <platform>" to set up a platform.\n`);
|
|
1005
|
+
});
|
|
592
1006
|
// ─── Helpers ──────────────────────────────────────────────
|
|
1007
|
+
function resolveDataDir(subcommandOpts) {
|
|
1008
|
+
return subcommandOpts.dir
|
|
1009
|
+
?? program.opts().dir
|
|
1010
|
+
?? process.env.CLAWCK_DIR
|
|
1011
|
+
?? '.clawck';
|
|
1012
|
+
}
|
|
593
1013
|
function formatDuration(mins) {
|
|
594
1014
|
if (mins < 1)
|
|
595
|
-
return
|
|
1015
|
+
return `${Math.round(mins * 60)}s`;
|
|
596
1016
|
if (mins < 60)
|
|
597
1017
|
return `${Math.round(mins)} min`;
|
|
598
1018
|
const h = Math.floor(mins / 60);
|
|
@@ -632,7 +1052,7 @@ function printEntryTable(entries) {
|
|
|
632
1052
|
console.log('\n No entries found.\n');
|
|
633
1053
|
return;
|
|
634
1054
|
}
|
|
635
|
-
const header = ` ${'ID'.padEnd(10)} ${'Task'.padEnd(42)} ${'Duration'.padEnd(10)} ${'Project'.padEnd(12)} ${'Agent'.padEnd(12)} ${'Time'}`;
|
|
1055
|
+
const header = ` ${'ID'.padEnd(10)} ${'Task'.padEnd(42)} ${'Duration'.padEnd(10)} ${'Project'.padEnd(12)} ${'Agent'.padEnd(12)} ${'OK'.padEnd(4)} ${'Time'}`;
|
|
636
1056
|
console.log(`\n${header}`);
|
|
637
1057
|
console.log(` ${'─'.repeat(header.length - 2)}`);
|
|
638
1058
|
for (const e of entries) {
|
|
@@ -642,7 +1062,8 @@ function printEntryTable(entries) {
|
|
|
642
1062
|
const dur = e.end ? formatDuration(durationMin) : 'running';
|
|
643
1063
|
const task = e.task.length > 40 ? e.task.slice(0, 37) + '...' : e.task;
|
|
644
1064
|
const time = new Date(e.start).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
645
|
-
|
|
1065
|
+
const approved = e.approved ? 'v' : '-';
|
|
1066
|
+
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
1067
|
}
|
|
647
1068
|
console.log(`\n ${entries.length} entries\n`);
|
|
648
1069
|
}
|
|
@@ -682,6 +1103,22 @@ function loadConfig(dir) {
|
|
|
682
1103
|
console.error(' Config error: "remote_sources" must be an array');
|
|
683
1104
|
process.exit(1);
|
|
684
1105
|
}
|
|
1106
|
+
if (fileConfig.webhooks !== undefined) {
|
|
1107
|
+
if (!Array.isArray(fileConfig.webhooks)) {
|
|
1108
|
+
console.error(' Config error: "webhooks" must be an array');
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
for (const [i, wh] of fileConfig.webhooks.entries()) {
|
|
1112
|
+
if (!wh.url || typeof wh.url !== 'string') {
|
|
1113
|
+
console.error(` Config error: "webhooks[${i}].url" must be a string`);
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
if (!Array.isArray(wh.events)) {
|
|
1117
|
+
console.error(` Config error: "webhooks[${i}].events" must be an array`);
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
685
1122
|
return {
|
|
686
1123
|
...types_1.DEFAULT_CONFIG,
|
|
687
1124
|
...fileConfig,
|