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.
@@ -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": "2.1.3",
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": "tsc",
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
- "@types/which": "^3.0.4",
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
- "typescript": "^5.2.2"
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",
@@ -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/vscode-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=vscode`;
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
- 'claude code',
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, _stdout, stderr) => {
136
- const output = _stdout.toString().trim() + stderr.toString().trim();
137
- if (output) {
138
- logger.error(output);
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);
@@ -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 = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop'];
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)) {