@techdivision/opencode-time-tracking 0.1.8 → 0.2.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 +7 -4
- package/package.json +1 -1
- package/src/hooks/EventHook.ts +37 -4
- package/src/services/ConfigLoader.ts +12 -5
- package/src/services/CsvWriter.ts +2 -1
- package/src/services/SessionManager.ts +20 -0
- package/src/types/CsvEntryData.ts +8 -0
- package/src/types/ModelInfo.ts +28 -0
- package/src/types/SessionData.ts +4 -0
- package/src/types/TimeTrackingConfig.ts +12 -1
package/README.md
CHANGED
|
@@ -14,13 +14,16 @@ Add to your `opencode.json`:
|
|
|
14
14
|
|
|
15
15
|
## Configuration
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Add the `time_tracking` section to your `.opencode/opencode-project.json`:
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
21
|
+
"$schema": "https://raw.githubusercontent.com/techdivision/opencode-plugins/main/schemas/opencode-project.json",
|
|
22
|
+
"time_tracking": {
|
|
23
|
+
"csv_file": "~/time_tracking/time-tracking.csv",
|
|
24
|
+
"user_email": "your@email.com",
|
|
25
|
+
"default_account_key": "YOUR_ACCOUNT_KEY"
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
```
|
|
26
29
|
|
package/package.json
CHANGED
package/src/hooks/EventHook.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview Event hook for session lifecycle and token tracking.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { Event } from "@opencode-ai/sdk"
|
|
5
|
+
import type { AssistantMessage, Event, Message } from "@opencode-ai/sdk"
|
|
6
6
|
|
|
7
7
|
import type { CsvWriter } from "../services/CsvWriter"
|
|
8
8
|
import type { SessionManager } from "../services/SessionManager"
|
|
@@ -12,6 +12,13 @@ import type { OpencodeClient } from "../types/OpencodeClient"
|
|
|
12
12
|
|
|
13
13
|
import { DescriptionGenerator } from "../utils/DescriptionGenerator"
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Properties for message.updated events.
|
|
17
|
+
*/
|
|
18
|
+
interface MessageUpdatedProperties {
|
|
19
|
+
info: Message
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* Extracts the summary title from the last user message.
|
|
17
24
|
*
|
|
@@ -60,10 +67,11 @@ async function extractSummaryTitle(
|
|
|
60
67
|
* @returns The event hook function
|
|
61
68
|
*
|
|
62
69
|
* @remarks
|
|
63
|
-
* Handles
|
|
70
|
+
* Handles three types of events:
|
|
64
71
|
*
|
|
65
|
-
* 1. **message.
|
|
66
|
-
* 2. **
|
|
72
|
+
* 1. **message.updated** - Tracks model from assistant messages
|
|
73
|
+
* 2. **message.part.updated** - Tracks token usage from step-finish parts
|
|
74
|
+
* 3. **session.idle** - Finalizes and exports the session
|
|
67
75
|
*
|
|
68
76
|
* @example
|
|
69
77
|
* ```typescript
|
|
@@ -78,6 +86,25 @@ export function createEventHook(
|
|
|
78
86
|
client: OpencodeClient
|
|
79
87
|
) {
|
|
80
88
|
return async ({ event }: { event: Event }): Promise<void> => {
|
|
89
|
+
// Track model from assistant messages
|
|
90
|
+
if (event.type === "message.updated") {
|
|
91
|
+
const props = event.properties as MessageUpdatedProperties
|
|
92
|
+
const message = props.info
|
|
93
|
+
|
|
94
|
+
if (message.role === "assistant") {
|
|
95
|
+
const assistantMsg = message as AssistantMessage
|
|
96
|
+
|
|
97
|
+
if (assistantMsg.modelID && assistantMsg.providerID) {
|
|
98
|
+
sessionManager.setModel(assistantMsg.sessionID, {
|
|
99
|
+
modelID: assistantMsg.modelID,
|
|
100
|
+
providerID: assistantMsg.providerID,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
// Track token usage from step-finish events
|
|
82
109
|
if (event.type === "message.part.updated") {
|
|
83
110
|
const props = event.properties as MessagePartUpdatedProperties
|
|
@@ -129,6 +156,11 @@ export function createEventHook(
|
|
|
129
156
|
session.tokenUsage.output +
|
|
130
157
|
session.tokenUsage.reasoning
|
|
131
158
|
|
|
159
|
+
// Format model as providerID/modelID
|
|
160
|
+
const modelString = session.model
|
|
161
|
+
? `${session.model.providerID}/${session.model.modelID}`
|
|
162
|
+
: null
|
|
163
|
+
|
|
132
164
|
try {
|
|
133
165
|
await csvWriter.write({
|
|
134
166
|
ticket: session.ticket,
|
|
@@ -138,6 +170,7 @@ export function createEventHook(
|
|
|
138
170
|
description,
|
|
139
171
|
notes: `Auto-tracked: ${toolSummary}`,
|
|
140
172
|
tokenUsage: session.tokenUsage,
|
|
173
|
+
model: modelString,
|
|
141
174
|
})
|
|
142
175
|
|
|
143
176
|
const minutes = Math.round(durationSeconds / 60)
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* @fileoverview Configuration loader for the time tracking plugin.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
OpencodeProjectConfig,
|
|
7
|
+
TimeTrackingConfig,
|
|
8
|
+
} from "../types/TimeTrackingConfig"
|
|
6
9
|
|
|
7
10
|
import "../types/Bun"
|
|
8
11
|
|
|
@@ -10,8 +13,8 @@ import "../types/Bun"
|
|
|
10
13
|
* Loads the plugin configuration from the project directory.
|
|
11
14
|
*
|
|
12
15
|
* @remarks
|
|
13
|
-
* The configuration file is expected at `.opencode/
|
|
14
|
-
* within the project directory.
|
|
16
|
+
* The configuration file is expected at `.opencode/opencode-project.json`
|
|
17
|
+
* within the project directory, with a `time_tracking` section.
|
|
15
18
|
*/
|
|
16
19
|
export class ConfigLoader {
|
|
17
20
|
/**
|
|
@@ -29,13 +32,17 @@ export class ConfigLoader {
|
|
|
29
32
|
* ```
|
|
30
33
|
*/
|
|
31
34
|
static async load(directory: string): Promise<TimeTrackingConfig | null> {
|
|
32
|
-
const configPath = `${directory}/.opencode/
|
|
35
|
+
const configPath = `${directory}/.opencode/opencode-project.json`
|
|
33
36
|
|
|
34
37
|
try {
|
|
35
38
|
const file = Bun.file(configPath)
|
|
36
39
|
|
|
37
40
|
if (await file.exists()) {
|
|
38
|
-
|
|
41
|
+
const projectConfig = (await file.json()) as OpencodeProjectConfig
|
|
42
|
+
|
|
43
|
+
if (projectConfig.time_tracking) {
|
|
44
|
+
return projectConfig.time_tracking
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
return null
|
|
@@ -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"
|
|
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"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Writes time tracking entries to a CSV file.
|
|
@@ -122,6 +122,7 @@ export class CsvWriter {
|
|
|
122
122
|
"",
|
|
123
123
|
CsvFormatter.escape(data.description),
|
|
124
124
|
CsvFormatter.escape(data.notes),
|
|
125
|
+
data.model ?? "",
|
|
125
126
|
]
|
|
126
127
|
|
|
127
128
|
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 { ModelInfo } from "../types/ModelInfo"
|
|
6
7
|
import type { SessionData } from "../types/SessionData"
|
|
7
8
|
import type { TokenUsage } from "../types/TokenUsage"
|
|
8
9
|
|
|
@@ -61,6 +62,7 @@ export class SessionManager {
|
|
|
61
62
|
cacheRead: 0,
|
|
62
63
|
cacheWrite: 0,
|
|
63
64
|
},
|
|
65
|
+
model: null,
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
this.sessions.set(sessionID, session)
|
|
@@ -126,4 +128,22 @@ export class SessionManager {
|
|
|
126
128
|
session.ticket = ticket
|
|
127
129
|
}
|
|
128
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Sets the model for a session.
|
|
134
|
+
*
|
|
135
|
+
* @param sessionID - The OpenCode session identifier
|
|
136
|
+
* @param model - The model information
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
* Only sets the model if it hasn't been set yet.
|
|
140
|
+
* The first model detected in a session is used.
|
|
141
|
+
*/
|
|
142
|
+
setModel(sessionID: string, model: ModelInfo): void {
|
|
143
|
+
const session = this.sessions.get(sessionID)
|
|
144
|
+
|
|
145
|
+
if (session && !session.model) {
|
|
146
|
+
session.model = model
|
|
147
|
+
}
|
|
148
|
+
}
|
|
129
149
|
}
|
|
@@ -28,4 +28,12 @@ export interface CsvEntryData {
|
|
|
28
28
|
|
|
29
29
|
/** Token consumption statistics */
|
|
30
30
|
tokenUsage: TokenUsage
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Model identifier in format `providerID/modelID`.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Examples: `anthropic/claude-opus-4`, `openai/gpt-5`
|
|
37
|
+
*/
|
|
38
|
+
model: string | null
|
|
31
39
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Model information type for tracking which LLM was used.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Information about the model used in a session.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Extracted from AssistantMessage events in the OpenCode SDK.
|
|
10
|
+
* Used to calculate token costs per model.
|
|
11
|
+
*/
|
|
12
|
+
export interface ModelInfo {
|
|
13
|
+
/**
|
|
14
|
+
* Model identifier (e.g., "claude-opus-4", "gpt-5").
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* This is the model name as reported by the provider.
|
|
18
|
+
*/
|
|
19
|
+
modelID: string
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Provider identifier (e.g., "anthropic", "openai").
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Combined with modelID to form the full model reference: `providerID/modelID`
|
|
26
|
+
*/
|
|
27
|
+
providerID: string
|
|
28
|
+
}
|
package/src/types/SessionData.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ActivityData } from "./ActivityData"
|
|
6
|
+
import type { ModelInfo } from "./ModelInfo"
|
|
6
7
|
import type { TokenUsage } from "./TokenUsage"
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -20,4 +21,7 @@ export interface SessionData {
|
|
|
20
21
|
|
|
21
22
|
/** Cumulative token usage for the session */
|
|
22
23
|
tokenUsage: TokenUsage
|
|
24
|
+
|
|
25
|
+
/** Model used in this session, or `null` if not detected */
|
|
26
|
+
model: ModelInfo | null
|
|
23
27
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Time tracking configuration from `.opencode/opencode-project.json`.
|
|
7
7
|
*/
|
|
8
8
|
export interface TimeTrackingConfig {
|
|
9
9
|
/**
|
|
@@ -23,3 +23,14 @@ export interface TimeTrackingConfig {
|
|
|
23
23
|
/** Default Jira account key for time entries */
|
|
24
24
|
default_account_key: string
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* OpenCode project configuration structure.
|
|
29
|
+
*/
|
|
30
|
+
export interface OpencodeProjectConfig {
|
|
31
|
+
/** JSON Schema reference */
|
|
32
|
+
$schema?: string
|
|
33
|
+
|
|
34
|
+
/** Time tracking configuration */
|
|
35
|
+
time_tracking?: TimeTrackingConfig
|
|
36
|
+
}
|