claude-code-wakatime 2.1.3 → 3.0.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/.claude-plugin/marketplace.json +25 -0
- package/.claude-plugin/plugin.json +17 -0
- package/README.md +58 -11
- package/dist/index.js +41704 -119
- package/hooks/hooks.json +92 -0
- package/package.json +12 -13
- package/src/dependencies.ts +2 -2
- package/src/index.ts +91 -10
- package/src/install-hooks.ts +10 -1
- package/dist/dependencies.js +0 -416
- package/dist/install-hooks.js +0 -53
- package/dist/logger.js +0 -74
- package/dist/options.js +0 -214
- package/dist/utils.js +0 -129
- package/dist/version.js +0 -5
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"SessionEnd": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"matcher": "",
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"PostToolUse": [
|
|
37
|
+
{
|
|
38
|
+
"matcher": "",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"UserPromptSubmit": [
|
|
48
|
+
{
|
|
49
|
+
"matcher": "",
|
|
50
|
+
"hooks": [
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"PreCompact": [
|
|
59
|
+
{
|
|
60
|
+
"matcher": "",
|
|
61
|
+
"hooks": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"SubagentStop": [
|
|
70
|
+
{
|
|
71
|
+
"matcher": "",
|
|
72
|
+
"hooks": [
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"Stop": [
|
|
81
|
+
{
|
|
82
|
+
"matcher": "",
|
|
83
|
+
"hooks": [
|
|
84
|
+
{
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-wakatime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "WakaTime plugin for Claude Code",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-code-wakatime": "dist/index.js"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node dist/install-hooks.js",
|
|
10
10
|
"prebuild": "node scripts/generate-version.js",
|
|
11
|
-
"build": "
|
|
11
|
+
"build": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js",
|
|
12
12
|
"watch": "npm run prebuild && tsc --watch",
|
|
13
13
|
"release:major": "npm version major && npm publish && git push && git push --tags",
|
|
14
14
|
"release:minor": "npm version minor && npm publish && git push && git push --tags",
|
|
@@ -24,28 +24,27 @@
|
|
|
24
24
|
"time-tracking",
|
|
25
25
|
"ai"
|
|
26
26
|
],
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"adm-zip": "0.5.16",
|
|
29
|
-
"request": "2.88.2",
|
|
30
|
-
"semver": "^7.7.2",
|
|
31
|
-
"which": "^5.0.0"
|
|
32
|
-
},
|
|
33
27
|
"devDependencies": {
|
|
28
|
+
"@types/adm-zip": "^0.5.7",
|
|
29
|
+
"@types/azdata": "^1.46.6",
|
|
34
30
|
"@types/eslint": "^8.44.7",
|
|
35
31
|
"@types/node": "^20.9.2",
|
|
36
32
|
"@types/pidusage": "^2.0.5",
|
|
37
33
|
"@types/prettier": "^2.7.3",
|
|
38
|
-
"@types/adm-zip": "^0.5.7",
|
|
39
|
-
"@types/azdata": "^1.46.6",
|
|
40
34
|
"@types/request": "^2.48.12",
|
|
41
35
|
"@types/semver": "^7.7.0",
|
|
36
|
+
"@types/which": "^3.0.4",
|
|
42
37
|
"@typescript-eslint/eslint-plugin": "6.11.0",
|
|
43
38
|
"@typescript-eslint/parser": "6.11.0",
|
|
44
|
-
"
|
|
45
|
-
"eslint": "^8.54.0",
|
|
39
|
+
"adm-zip": "0.5.16",
|
|
46
40
|
"azdata": "^1.0.0",
|
|
41
|
+
"esbuild": "^0.25.12",
|
|
42
|
+
"eslint": "^8.54.0",
|
|
47
43
|
"prettier": "^3.1.0",
|
|
48
|
-
"
|
|
44
|
+
"request": "2.88.2",
|
|
45
|
+
"semver": "^7.7.2",
|
|
46
|
+
"typescript": "^5.2.2",
|
|
47
|
+
"which": "^5.0.0"
|
|
49
48
|
},
|
|
50
49
|
"author": "WakaTime",
|
|
51
50
|
"license": "BSD-3-Clause",
|
package/src/dependencies.ts
CHANGED
|
@@ -153,7 +153,7 @@ export class Dependencies {
|
|
|
153
153
|
url: this.githubReleasesUrl,
|
|
154
154
|
json: true,
|
|
155
155
|
headers: {
|
|
156
|
-
'User-Agent': 'github.com/wakatime/
|
|
156
|
+
'User-Agent': 'github.com/wakatime/claude-code-wakatime',
|
|
157
157
|
},
|
|
158
158
|
};
|
|
159
159
|
this.logger.debug(`Fetching latest wakatime-cli version from GitHub API: ${options.url}`);
|
|
@@ -378,7 +378,7 @@ export class Dependencies {
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
private reportMissingPlatformSupport(osname: string, architecture: string): void {
|
|
381
|
-
const url = `https://api.wakatime.com/api/v1/cli-missing?osname=${osname}&architecture=${architecture}&plugin=
|
|
381
|
+
const url = `https://api.wakatime.com/api/v1/cli-missing?osname=${osname}&architecture=${architecture}&plugin=claude-code`;
|
|
382
382
|
const proxy = this.options.getSetting('settings', 'proxy');
|
|
383
383
|
const noSSLVerify = this.options.getSetting('settings', 'no_ssl_verify');
|
|
384
384
|
let options: request.CoreOptions & { url: string } = { url: url };
|
package/src/index.ts
CHANGED
|
@@ -60,6 +60,81 @@ function getLastHeartbeat() {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function getModifiedFile(transcriptPath: string): string | undefined {
|
|
64
|
+
try {
|
|
65
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = fs.readFileSync(transcriptPath, 'utf-8');
|
|
70
|
+
const lines = content.split('\n');
|
|
71
|
+
const fileLineChanges = new Map<string, number>();
|
|
72
|
+
|
|
73
|
+
const lastHeartbeatAt = getLastHeartbeat();
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (line.trim()) {
|
|
76
|
+
try {
|
|
77
|
+
const logEntry = JSON.parse(line);
|
|
78
|
+
if (!logEntry.timestamp) continue;
|
|
79
|
+
|
|
80
|
+
const entryTimestamp = new Date(logEntry.timestamp).getTime() / 1000;
|
|
81
|
+
if (entryTimestamp >= lastHeartbeatAt) {
|
|
82
|
+
let filePath: string | undefined;
|
|
83
|
+
|
|
84
|
+
// Check for file paths in tool use results
|
|
85
|
+
if (logEntry.toolUse?.parameters?.file_path) {
|
|
86
|
+
filePath = logEntry.toolUse.parameters.file_path;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for file paths in tool use results for multi-edit
|
|
90
|
+
if (logEntry.toolUse?.parameters?.edits) {
|
|
91
|
+
filePath = logEntry.toolUse.parameters.file_path;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check for file paths and line changes in structured patch
|
|
95
|
+
if (logEntry.toolUseResult?.structuredPatch) {
|
|
96
|
+
const patches = logEntry.toolUseResult.structuredPatch;
|
|
97
|
+
for (const patch of patches) {
|
|
98
|
+
if (patch.file) {
|
|
99
|
+
filePath = patch.file as string;
|
|
100
|
+
if (patch.newLines !== undefined && patch.oldLines !== undefined) {
|
|
101
|
+
const lineChanges = Math.abs(patch.newLines - patch.oldLines);
|
|
102
|
+
fileLineChanges.set(filePath, (fileLineChanges.get(filePath) || 0) + lineChanges);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (filePath && !fileLineChanges.has(filePath)) {
|
|
109
|
+
fileLineChanges.set(filePath, 0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// ignore
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (fileLineChanges.size === 0) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Find file with most line changes
|
|
123
|
+
let maxChanges = 0;
|
|
124
|
+
let mostChangedFile: string | undefined;
|
|
125
|
+
for (const [file, changes] of fileLineChanges.entries()) {
|
|
126
|
+
if (changes > maxChanges) {
|
|
127
|
+
maxChanges = changes;
|
|
128
|
+
mostChangedFile = file;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return mostChangedFile;
|
|
133
|
+
} catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
63
138
|
function calculateLineChanges(transcriptPath: string): number {
|
|
64
139
|
try {
|
|
65
140
|
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
@@ -108,11 +183,19 @@ function updateState() {
|
|
|
108
183
|
function sendHeartbeat(inp: Input | undefined) {
|
|
109
184
|
const projectFolder = inp?.cwd;
|
|
110
185
|
try {
|
|
186
|
+
let entity = 'claude code';
|
|
187
|
+
if (inp?.transcript_path) {
|
|
188
|
+
const modifiedFile = getModifiedFile(inp.transcript_path);
|
|
189
|
+
if (modifiedFile) {
|
|
190
|
+
entity = modifiedFile;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
111
194
|
const args: string[] = [
|
|
112
195
|
'--entity',
|
|
113
|
-
|
|
196
|
+
entity,
|
|
114
197
|
'--entity-type',
|
|
115
|
-
'app',
|
|
198
|
+
entity === 'claude code' ? 'app' : 'file',
|
|
116
199
|
'--category',
|
|
117
200
|
'ai coding',
|
|
118
201
|
'--plugin',
|
|
@@ -131,15 +214,13 @@ function sendHeartbeat(inp: Input | undefined) {
|
|
|
131
214
|
}
|
|
132
215
|
}
|
|
133
216
|
|
|
217
|
+
logger.debug(`Sending heartbeat: ${args}`);
|
|
218
|
+
|
|
134
219
|
const options = Utils.buildOptions();
|
|
135
|
-
execFile(WAKATIME_CLI, args, options, (error,
|
|
136
|
-
const output =
|
|
137
|
-
if (output)
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
if (!(error != null)) {
|
|
141
|
-
logger.debug(`Sending heartbeat: ${args}`);
|
|
142
|
-
}
|
|
220
|
+
execFile(WAKATIME_CLI, args, options, (error, stdout, stderr) => {
|
|
221
|
+
const output = stdout.toString().trim() + stderr.toString().trim();
|
|
222
|
+
if (output) logger.error(output);
|
|
223
|
+
if (error) logger.error(error.toString());
|
|
143
224
|
});
|
|
144
225
|
} catch (err: any) {
|
|
145
226
|
logger.errorException(err);
|
package/src/install-hooks.ts
CHANGED
|
@@ -3,7 +3,16 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
|
|
5
5
|
const CLAUDE_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json');
|
|
6
|
-
const HOOK_EVENTS = [
|
|
6
|
+
const HOOK_EVENTS = [
|
|
7
|
+
'PreToolUse',
|
|
8
|
+
'PostToolUse',
|
|
9
|
+
'SessionStart',
|
|
10
|
+
'SessionEnd',
|
|
11
|
+
'UserPromptSubmit',
|
|
12
|
+
'PreCompact',
|
|
13
|
+
'SubagentStop',
|
|
14
|
+
'Stop',
|
|
15
|
+
];
|
|
7
16
|
|
|
8
17
|
function loadSettings(): any {
|
|
9
18
|
if (!fs.existsSync(CLAUDE_SETTINGS)) {
|