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 +6 -1
- package/dist/index.js +58 -2
- package/dist/install-hooks.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -2
- package/src/install-hooks.ts +9 -10
package/README.md
CHANGED
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
1. `npm install -g claude-code-wakatime`
|
|
8
8
|
|
|
9
|
-
2.
|
|
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
|

|
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
|
}
|
package/dist/install-hooks.js
CHANGED
|
@@ -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
package/package.json
CHANGED
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
|
}
|
package/src/install-hooks.ts
CHANGED
|
@@ -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(
|
|
42
|
-
existingHook
|
|
43
|
-
|
|
44
|
-
|
|
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;
|