claude-code-wakatime 2.0.0 → 2.1.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 CHANGED
@@ -6,12 +6,17 @@
6
6
 
7
7
  1. `npm install -g claude-code-wakatime`
8
8
 
9
- 2. Install WakaTime for [VS Code](https://wakatime.com/vs-code) to setup your `~/.wakatime/` folder and api key.
9
+ 2. Add your [api key](https://wakatime.com/settings/api-key) to `~/.wakatime.cfg`:
10
+
11
+ [settings]
12
+ api_key = waka_123
10
13
 
11
14
  4. Use Claude Code and your AI coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com)
12
15
 
13
16
  ## Usage
14
17
 
18
+ New: See the lines of code generated by AI on your dashboard!
19
+
15
20
  Visit [https://wakatime.com](https://wakatime.com) to see your coding activity.
16
21
 
17
22
  ![Project Overview](https://wakatime.com/static/img/ScreenShots/Screen-Shot-2016-03-21.png)
package/dist/index.js CHANGED
@@ -15,7 +15,10 @@ const utils_1 = require("./utils");
15
15
  const logger_1 = require("./logger");
16
16
  const STATE_FILE = path_1.default.join(os_1.default.homedir(), '.wakatime', 'claude-code.json');
17
17
  const WAKATIME_CLI = path_1.default.join(os_1.default.homedir(), '.wakatime', 'wakatime-cli');
18
- function shouldSendHeartbeat() {
18
+ function shouldSendHeartbeat(inp) {
19
+ if (inp?.hook_event_name === 'Stop') {
20
+ return true;
21
+ }
19
22
  try {
20
23
  const last = JSON.parse(fs_1.default.readFileSync(STATE_FILE, 'utf-8')).lastHeartbeatAt ?? utils_1.Utils.timestamp();
21
24
  return utils_1.Utils.timestamp() - last >= 60;
@@ -37,6 +40,52 @@ function parseInput() {
37
40
  }
38
41
  return undefined;
39
42
  }
43
+ function getLastHeartbeat() {
44
+ try {
45
+ const stateData = JSON.parse(fs_1.default.readFileSync(STATE_FILE, 'utf-8'));
46
+ return stateData.lastHeartbeatAt ?? 0;
47
+ }
48
+ catch {
49
+ return 0;
50
+ }
51
+ }
52
+ function calculateLineChanges(transcriptPath) {
53
+ try {
54
+ if (!transcriptPath || !fs_1.default.existsSync(transcriptPath)) {
55
+ return 0;
56
+ }
57
+ const content = fs_1.default.readFileSync(transcriptPath, 'utf-8');
58
+ const lines = content.split('\n');
59
+ let totalLineChanges = 0;
60
+ const lastHeartbeatAt = getLastHeartbeat();
61
+ for (const line of lines) {
62
+ if (line.trim()) {
63
+ try {
64
+ const logEntry = JSON.parse(line);
65
+ // Only count changes since last heartbeat
66
+ if (logEntry.timestamp && logEntry.toolUseResult?.structuredPatch) {
67
+ const entryTimestamp = new Date(logEntry.timestamp).getTime() / 1000;
68
+ if (entryTimestamp >= lastHeartbeatAt) {
69
+ const patches = logEntry.toolUseResult.structuredPatch;
70
+ for (const patch of patches) {
71
+ if (patch.newLines !== undefined && patch.oldLines !== undefined) {
72
+ totalLineChanges += patch.newLines - patch.oldLines;
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ }
82
+ }
83
+ return totalLineChanges;
84
+ }
85
+ catch {
86
+ return 0;
87
+ }
88
+ }
40
89
  function updateState() {
41
90
  fs_1.default.mkdirSync(path_1.default.dirname(STATE_FILE), { recursive: true });
42
91
  fs_1.default.writeFileSync(STATE_FILE, JSON.stringify({ lastHeartbeatAt: utils_1.Utils.timestamp() }, null, 2));
@@ -58,6 +107,13 @@ function sendHeartbeat(inp, logger) {
58
107
  args.push('--project-folder');
59
108
  args.push(projectFolder);
60
109
  }
110
+ if (inp?.transcript_path) {
111
+ const lineChanges = calculateLineChanges(inp.transcript_path);
112
+ if (lineChanges !== 0) {
113
+ args.push('--ai-line-changes');
114
+ args.push(lineChanges.toString());
115
+ }
116
+ }
61
117
  (0, child_process_1.execFileSync)(WAKATIME_CLI, args);
62
118
  }
63
119
  catch (err) {
@@ -81,7 +137,7 @@ function main() {
81
137
  if (inp?.hook_event_name === 'SessionStart') {
82
138
  deps.checkAndInstallCli();
83
139
  }
84
- if (shouldSendHeartbeat()) {
140
+ if (shouldSendHeartbeat(inp)) {
85
141
  sendHeartbeat(inp, logger);
86
142
  updateState();
87
143
  }
@@ -7,7 +7,7 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const os_1 = __importDefault(require("os"));
9
9
  const CLAUDE_SETTINGS = path_1.default.join(os_1.default.homedir(), '.claude', 'settings.json');
10
- const HOOK_EVENTS = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart'];
10
+ const HOOK_EVENTS = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop'];
11
11
  function loadSettings() {
12
12
  if (!fs_1.default.existsSync(CLAUDE_SETTINGS)) {
13
13
  return {};
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // This file is auto-generated during build. Do not edit manually.
5
- exports.VERSION = '1.0.1';
5
+ exports.VERSION = '2.0.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-wakatime",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "WakaTime plugin for Claude Code",
5
5
  "bin": {
6
6
  "claude-code-wakatime": "dist/index.js"
package/src/index.ts CHANGED
@@ -24,7 +24,11 @@ type Input = {
24
24
  hook_event_name: string;
25
25
  };
26
26
 
27
- function shouldSendHeartbeat(): boolean {
27
+ function shouldSendHeartbeat(inp?: Input): boolean {
28
+ if (inp?.hook_event_name === 'Stop') {
29
+ return true;
30
+ }
31
+
28
32
  try {
29
33
  const last = (JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')) as State).lastHeartbeatAt ?? Utils.timestamp();
30
34
  return Utils.timestamp() - last >= 60;
@@ -152,7 +156,7 @@ function main() {
152
156
  deps.checkAndInstallCli();
153
157
  }
154
158
 
155
- if (shouldSendHeartbeat()) {
159
+ if (shouldSendHeartbeat(inp)) {
156
160
  sendHeartbeat(inp, logger);
157
161
  updateState();
158
162
  }
@@ -3,7 +3,7 @@ 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'];
6
+ const HOOK_EVENTS = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop'];
7
7
 
8
8
  function loadSettings(): any {
9
9
  if (!fs.existsSync(CLAUDE_SETTINGS)) {
@@ -33,19 +33,18 @@ function installHooks(): void {
33
33
  };
34
34
 
35
35
  let hookAlreadyExists = true;
36
-
36
+
37
37
  for (const event of HOOK_EVENTS) {
38
38
  settings.hooks[event] = settings.hooks[event] || [];
39
-
39
+
40
40
  // Check if a hook with the same command already exists
41
- const existingHook = settings.hooks[event].find((existingHook: any) =>
42
- existingHook.hooks &&
43
- Array.isArray(existingHook.hooks) &&
44
- existingHook.hooks.some((hookItem: any) =>
45
- hookItem.command === 'claude-code-wakatime'
46
- )
41
+ const existingHook = settings.hooks[event].find(
42
+ (existingHook: any) =>
43
+ existingHook.hooks &&
44
+ Array.isArray(existingHook.hooks) &&
45
+ existingHook.hooks.some((hookItem: any) => hookItem.command === 'claude-code-wakatime'),
47
46
  );
48
-
47
+
49
48
  if (!existingHook) {
50
49
  settings.hooks[event].push(hook);
51
50
  hookAlreadyExists = false;