@techdivision/opencode-time-tracking 0.2.0 → 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 +20 -1
- package/package.json +1 -1
- package/src/Plugin.ts +12 -6
- package/src/hooks/EventHook.ts +11 -1
- package/src/services/ConfigLoader.ts +21 -1
- package/src/services/CsvWriter.ts +2 -1
- package/src/services/SessionManager.ts +24 -0
- package/src/types/AgentInfo.ts +14 -0
- package/src/types/CsvEntryData.ts +9 -0
- package/src/types/MessagePartUpdatedProperties.ts +5 -2
- package/src/types/SessionData.ts +4 -0
- package/src/types/TimeTrackingConfig.ts +26 -6
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Add to your `opencode.json`:
|
|
|
14
14
|
|
|
15
15
|
## Configuration
|
|
16
16
|
|
|
17
|
+
### 1. Project Configuration
|
|
18
|
+
|
|
17
19
|
Add the `time_tracking` section to your `.opencode/opencode-project.json`:
|
|
18
20
|
|
|
19
21
|
```json
|
|
@@ -21,12 +23,29 @@ Add the `time_tracking` section to your `.opencode/opencode-project.json`:
|
|
|
21
23
|
"$schema": "https://raw.githubusercontent.com/techdivision/opencode-plugins/main/schemas/opencode-project.json",
|
|
22
24
|
"time_tracking": {
|
|
23
25
|
"csv_file": "~/time_tracking/time-tracking.csv",
|
|
24
|
-
"user_email": "your@email.com",
|
|
25
26
|
"default_account_key": "YOUR_ACCOUNT_KEY"
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
### 2. User Email (Environment Variable)
|
|
32
|
+
|
|
33
|
+
Set your user email via the `OPENCODE_USER_EMAIL` environment variable.
|
|
34
|
+
|
|
35
|
+
Add to your `.env` file (recommended):
|
|
36
|
+
|
|
37
|
+
```env
|
|
38
|
+
OPENCODE_USER_EMAIL=your@email.com
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or export in your shell:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export OPENCODE_USER_EMAIL=your@email.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If not set, the system username is used as fallback.
|
|
48
|
+
|
|
30
49
|
## How it works
|
|
31
50
|
|
|
32
51
|
- Tracks tool executions during each session turn
|
package/package.json
CHANGED
package/src/Plugin.ts
CHANGED
|
@@ -25,20 +25,27 @@ import { createToolExecuteAfterHook } from "./hooks/ToolExecuteAfterHook"
|
|
|
25
25
|
* - Token consumption (input/output/reasoning tokens)
|
|
26
26
|
* - Ticket references (extracted from user messages or todos)
|
|
27
27
|
*
|
|
28
|
-
* Data is exported to a CSV file configured in `.opencode/
|
|
28
|
+
* Data is exported to a CSV file configured in `.opencode/opencode-project.json`.
|
|
29
29
|
*
|
|
30
30
|
* @param input - Plugin input containing client, directory, and other context
|
|
31
31
|
* @returns Hooks object with event and tool.execute.after handlers
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
34
34
|
* ```json
|
|
35
|
-
* // .opencode/
|
|
35
|
+
* // .opencode/opencode-project.json
|
|
36
36
|
* {
|
|
37
|
-
* "
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* "time_tracking": {
|
|
38
|
+
* "csv_file": "~/worklogs/time.csv",
|
|
39
|
+
* "default_account_key": "ACCOUNT-1"
|
|
40
|
+
* }
|
|
40
41
|
* }
|
|
41
42
|
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```bash
|
|
46
|
+
* # .env - Set user email via environment variable
|
|
47
|
+
* OPENCODE_USER_EMAIL=user@example.com
|
|
48
|
+
* ```
|
|
42
49
|
*/
|
|
43
50
|
export const plugin: Plugin = async ({
|
|
44
51
|
client,
|
|
@@ -48,7 +55,6 @@ export const plugin: Plugin = async ({
|
|
|
48
55
|
|
|
49
56
|
if (!config) {
|
|
50
57
|
// Silently return empty hooks if no config found
|
|
51
|
-
// Toast notifications don't work during plugin initialization
|
|
52
58
|
return {}
|
|
53
59
|
}
|
|
54
60
|
|
package/src/hooks/EventHook.ts
CHANGED
|
@@ -105,11 +105,17 @@ export function createEventHook(
|
|
|
105
105
|
return
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
// Track token usage from
|
|
108
|
+
// Track token usage and agent from message part events
|
|
109
109
|
if (event.type === "message.part.updated") {
|
|
110
110
|
const props = event.properties as MessagePartUpdatedProperties
|
|
111
111
|
const part = props.part
|
|
112
112
|
|
|
113
|
+
// Track agent from agent parts (only first agent is stored)
|
|
114
|
+
if (part.type === "agent" && part.sessionID && part.name) {
|
|
115
|
+
sessionManager.setAgent(part.sessionID, part.name)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Track token usage from step-finish events
|
|
113
119
|
if (part.type === "step-finish" && part.sessionID && part.tokens) {
|
|
114
120
|
sessionManager.addTokenUsage(part.sessionID, {
|
|
115
121
|
input: part.tokens.input,
|
|
@@ -161,6 +167,9 @@ export function createEventHook(
|
|
|
161
167
|
? `${session.model.providerID}/${session.model.modelID}`
|
|
162
168
|
: null
|
|
163
169
|
|
|
170
|
+
// Get agent name if available
|
|
171
|
+
const agentString = session.agent?.name ?? null
|
|
172
|
+
|
|
164
173
|
try {
|
|
165
174
|
await csvWriter.write({
|
|
166
175
|
ticket: session.ticket,
|
|
@@ -171,6 +180,7 @@ export function createEventHook(
|
|
|
171
180
|
notes: `Auto-tracked: ${toolSummary}`,
|
|
172
181
|
tokenUsage: session.tokenUsage,
|
|
173
182
|
model: modelString,
|
|
183
|
+
agent: agentString,
|
|
174
184
|
})
|
|
175
185
|
|
|
176
186
|
const minutes = Math.round(durationSeconds / 60)
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @fileoverview Configuration loader for the time tracking plugin.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { userInfo } from "os"
|
|
6
|
+
|
|
5
7
|
import type {
|
|
6
8
|
OpencodeProjectConfig,
|
|
7
9
|
TimeTrackingConfig,
|
|
@@ -9,12 +11,21 @@ import type {
|
|
|
9
11
|
|
|
10
12
|
import "../types/Bun"
|
|
11
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Environment variable name for user email.
|
|
16
|
+
*/
|
|
17
|
+
const ENV_USER_EMAIL = "OPENCODE_USER_EMAIL"
|
|
18
|
+
|
|
12
19
|
/**
|
|
13
20
|
* Loads the plugin configuration from the project directory.
|
|
14
21
|
*
|
|
15
22
|
* @remarks
|
|
16
23
|
* The configuration file is expected at `.opencode/opencode-project.json`
|
|
17
24
|
* within the project directory, with a `time_tracking` section.
|
|
25
|
+
*
|
|
26
|
+
* The `user_email` is resolved from:
|
|
27
|
+
* 1. `OPENCODE_USER_EMAIL` environment variable
|
|
28
|
+
* 2. System username (fallback)
|
|
18
29
|
*/
|
|
19
30
|
export class ConfigLoader {
|
|
20
31
|
/**
|
|
@@ -28,6 +39,7 @@ export class ConfigLoader {
|
|
|
28
39
|
* const config = await ConfigLoader.load("/path/to/project")
|
|
29
40
|
* if (config) {
|
|
30
41
|
* console.log(config.csv_file)
|
|
42
|
+
* console.log(config.user_email) // Resolved from ENV or system username
|
|
31
43
|
* }
|
|
32
44
|
* ```
|
|
33
45
|
*/
|
|
@@ -41,7 +53,15 @@ export class ConfigLoader {
|
|
|
41
53
|
const projectConfig = (await file.json()) as OpencodeProjectConfig
|
|
42
54
|
|
|
43
55
|
if (projectConfig.time_tracking) {
|
|
44
|
-
|
|
56
|
+
const jsonConfig = projectConfig.time_tracking
|
|
57
|
+
|
|
58
|
+
// Resolve user_email from environment variable or fallback to system username
|
|
59
|
+
const userEmail = process.env[ENV_USER_EMAIL] || userInfo().username
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...jsonConfig,
|
|
63
|
+
user_email: userEmail,
|
|
64
|
+
}
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
67
|
|
|
@@ -18,7 +18,7 @@ import "../types/Bun"
|
|
|
18
18
|
* Compatible with Jira/Tempo time tracking import.
|
|
19
19
|
*/
|
|
20
20
|
const CSV_HEADER =
|
|
21
|
-
"id,start_date,end_date,user,ticket_name,issue_key,account_key,start_time,end_time,duration_seconds,tokens_used,tokens_remaining,story_points,description,notes,model"
|
|
21
|
+
"id,start_date,end_date,user,ticket_name,issue_key,account_key,start_time,end_time,duration_seconds,tokens_used,tokens_remaining,story_points,description,notes,model,agent"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Writes time tracking entries to a CSV file.
|
|
@@ -123,6 +123,7 @@ export class CsvWriter {
|
|
|
123
123
|
CsvFormatter.escape(data.description),
|
|
124
124
|
CsvFormatter.escape(data.notes),
|
|
125
125
|
data.model ?? "",
|
|
126
|
+
data.agent ?? "",
|
|
126
127
|
]
|
|
127
128
|
|
|
128
129
|
const csvLine = fields.map((f) => `"${f}"`).join(",")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ActivityData } from "../types/ActivityData"
|
|
6
|
+
import type { AgentInfo } from "../types/AgentInfo"
|
|
6
7
|
import type { ModelInfo } from "../types/ModelInfo"
|
|
7
8
|
import type { SessionData } from "../types/SessionData"
|
|
8
9
|
import type { TokenUsage } from "../types/TokenUsage"
|
|
@@ -63,6 +64,7 @@ export class SessionManager {
|
|
|
63
64
|
cacheWrite: 0,
|
|
64
65
|
},
|
|
65
66
|
model: null,
|
|
67
|
+
agent: null,
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
this.sessions.set(sessionID, session)
|
|
@@ -146,4 +148,26 @@ export class SessionManager {
|
|
|
146
148
|
session.model = model
|
|
147
149
|
}
|
|
148
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sets the agent for a session.
|
|
154
|
+
*
|
|
155
|
+
* @param sessionID - The OpenCode session identifier
|
|
156
|
+
* @param agentName - The agent name (e.g., "@developer")
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* Only sets the agent if it hasn't been set yet.
|
|
160
|
+
* The first agent detected in a session is used (primary agent).
|
|
161
|
+
*/
|
|
162
|
+
setAgent(sessionID: string, agentName: string): void {
|
|
163
|
+
const session = this.sessions.get(sessionID)
|
|
164
|
+
|
|
165
|
+
if (session && !session.agent) {
|
|
166
|
+
const agent: AgentInfo = {
|
|
167
|
+
name: agentName,
|
|
168
|
+
timestamp: Date.now(),
|
|
169
|
+
}
|
|
170
|
+
session.agent = agent
|
|
171
|
+
}
|
|
172
|
+
}
|
|
149
173
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent information type for tracking which agent executed work.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Information about an agent that was active during a session.
|
|
7
|
+
*/
|
|
8
|
+
export interface AgentInfo {
|
|
9
|
+
/** The agent name (e.g., "@developer", "@reviewer") */
|
|
10
|
+
name: string
|
|
11
|
+
|
|
12
|
+
/** Unix timestamp in milliseconds when the agent became active */
|
|
13
|
+
timestamp: number
|
|
14
|
+
}
|
|
@@ -36,4 +36,13 @@ export interface CsvEntryData {
|
|
|
36
36
|
* Examples: `anthropic/claude-opus-4`, `openai/gpt-5`
|
|
37
37
|
*/
|
|
38
38
|
model: string | null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Agent name that performed the work.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Examples: `@developer`, `@reviewer`
|
|
45
|
+
* Only the first/primary agent is tracked.
|
|
46
|
+
*/
|
|
47
|
+
agent: string | null
|
|
39
48
|
}
|
|
@@ -10,13 +10,16 @@ import type { StepFinishPart } from "./StepFinishPart"
|
|
|
10
10
|
export interface MessagePartUpdatedProperties {
|
|
11
11
|
/** The updated message part */
|
|
12
12
|
part: {
|
|
13
|
-
/** The type of the part */
|
|
13
|
+
/** The type of the part (e.g., "step-finish", "agent") */
|
|
14
14
|
type: string
|
|
15
15
|
|
|
16
|
-
/** Session ID (present on step-finish parts) */
|
|
16
|
+
/** Session ID (present on step-finish and agent parts) */
|
|
17
17
|
sessionID?: string
|
|
18
18
|
|
|
19
19
|
/** Token usage (present on step-finish parts) */
|
|
20
20
|
tokens?: StepFinishPart["tokens"]
|
|
21
|
+
|
|
22
|
+
/** Agent name (present on agent parts) */
|
|
23
|
+
name?: string
|
|
21
24
|
}
|
|
22
25
|
}
|
package/src/types/SessionData.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ActivityData } from "./ActivityData"
|
|
6
|
+
import type { AgentInfo } from "./AgentInfo"
|
|
6
7
|
import type { ModelInfo } from "./ModelInfo"
|
|
7
8
|
import type { TokenUsage } from "./TokenUsage"
|
|
8
9
|
|
|
@@ -24,4 +25,7 @@ export interface SessionData {
|
|
|
24
25
|
|
|
25
26
|
/** Model used in this session, or `null` if not detected */
|
|
26
27
|
model: ModelInfo | null
|
|
28
|
+
|
|
29
|
+
/** First agent used in this session, or `null` if not detected */
|
|
30
|
+
agent: AgentInfo | null
|
|
27
31
|
}
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Time tracking configuration
|
|
6
|
+
* Time tracking configuration as stored in `.opencode/opencode-project.json`.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The `user_email` field is not stored in the JSON file.
|
|
10
|
+
* It is resolved from `OPENCODE_USER_EMAIL` environment variable
|
|
11
|
+
* or falls back to the system username.
|
|
7
12
|
*/
|
|
8
|
-
export interface
|
|
13
|
+
export interface TimeTrackingJsonConfig {
|
|
9
14
|
/**
|
|
10
15
|
* Path to the CSV output file.
|
|
11
16
|
*
|
|
@@ -17,13 +22,28 @@ export interface TimeTrackingConfig {
|
|
|
17
22
|
*/
|
|
18
23
|
csv_file: string
|
|
19
24
|
|
|
20
|
-
/** Email address of the user for the worklog */
|
|
21
|
-
user_email: string
|
|
22
|
-
|
|
23
25
|
/** Default Jira account key for time entries */
|
|
24
26
|
default_account_key: string
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Resolved time tracking configuration used at runtime.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* Extends `TimeTrackingJsonConfig` with the resolved `user_email` field.
|
|
34
|
+
*/
|
|
35
|
+
export interface TimeTrackingConfig extends TimeTrackingJsonConfig {
|
|
36
|
+
/**
|
|
37
|
+
* User email for the worklog.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* Resolved from (in order of priority):
|
|
41
|
+
* 1. `OPENCODE_USER_EMAIL` environment variable
|
|
42
|
+
* 2. System username (via `os.userInfo().username`)
|
|
43
|
+
*/
|
|
44
|
+
user_email: string
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
/**
|
|
28
48
|
* OpenCode project configuration structure.
|
|
29
49
|
*/
|
|
@@ -32,5 +52,5 @@ export interface OpencodeProjectConfig {
|
|
|
32
52
|
$schema?: string
|
|
33
53
|
|
|
34
54
|
/** Time tracking configuration */
|
|
35
|
-
time_tracking?:
|
|
55
|
+
time_tracking?: TimeTrackingJsonConfig
|
|
36
56
|
}
|