@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 CHANGED
@@ -14,13 +14,16 @@ Add to your `opencode.json`:
14
14
 
15
15
  ## Configuration
16
16
 
17
- Create `.opencode/time-tracking.json` in your project:
17
+ Add the `time_tracking` section to your `.opencode/opencode-project.json`:
18
18
 
19
19
  ```json
20
20
  {
21
- "csv_file": "~/time_tracking/time-tracking.csv",
22
- "user_email": "your@email.com",
23
- "default_account_key": "YOUR_ACCOUNT_KEY"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techdivision/opencode-time-tracking",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Automatic time tracking plugin for OpenCode - tracks session duration and tool usage to CSV",
5
5
  "main": "src/Plugin.ts",
6
6
  "types": "src/Plugin.ts",
@@ -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 two types of events:
70
+ * Handles three types of events:
64
71
  *
65
- * 1. **message.part.updated** - Tracks token usage from step-finish parts
66
- * 2. **session.idle** - Finalizes and exports the session
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 { TimeTrackingConfig } from "../types/TimeTrackingConfig"
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/time-tracking.json`
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/time-tracking.json`
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
- return (await file.json()) as TimeTrackingConfig
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
+ }
@@ -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
- * Plugin configuration loaded from `.opencode/time-tracking.json`.
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
+ }