opencode-time-tracking 0.1.5
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/AGENTS.md +247 -0
- package/README.md +45 -0
- package/package.json +26 -0
- package/src/Plugin.ts +68 -0
- package/src/hooks/EventHook.ts +163 -0
- package/src/hooks/ToolExecuteAfterHook.ts +78 -0
- package/src/services/ConfigLoader.ts +46 -0
- package/src/services/CsvWriter.ts +136 -0
- package/src/services/SessionManager.ts +129 -0
- package/src/services/TicketExtractor.ts +156 -0
- package/src/types/ActivityData.ts +17 -0
- package/src/types/Bun.ts +40 -0
- package/src/types/CsvEntryData.ts +31 -0
- package/src/types/MessageInfo.ts +16 -0
- package/src/types/MessagePart.ts +14 -0
- package/src/types/MessagePartUpdatedProperties.ts +22 -0
- package/src/types/MessageSummary.ts +14 -0
- package/src/types/MessageWithParts.ts +17 -0
- package/src/types/OpencodeClient.ts +14 -0
- package/src/types/SessionData.ts +23 -0
- package/src/types/StepFinishPart.ts +39 -0
- package/src/types/TimeTrackingConfig.ts +25 -0
- package/src/types/Todo.ts +11 -0
- package/src/types/TokenUsage.ts +23 -0
- package/src/types/ToolExecuteAfterInput.ts +17 -0
- package/src/types/ToolExecuteAfterOutput.ts +17 -0
- package/src/utils/CsvFormatter.ts +57 -0
- package/src/utils/DescriptionGenerator.ts +119 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CSV writer for exporting time tracking data.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { randomUUID } from "crypto"
|
|
6
|
+
import { mkdir } from "fs/promises"
|
|
7
|
+
import { dirname } from "path"
|
|
8
|
+
|
|
9
|
+
import type { CsvEntryData } from "../types/CsvEntryData"
|
|
10
|
+
import type { TimeTrackingConfig } from "../types/TimeTrackingConfig"
|
|
11
|
+
|
|
12
|
+
import { CsvFormatter } from "../utils/CsvFormatter"
|
|
13
|
+
|
|
14
|
+
import "../types/Bun"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CSV header row for the worklog export file.
|
|
18
|
+
* Compatible with Jira/Tempo time tracking import.
|
|
19
|
+
*/
|
|
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"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Writes time tracking entries to a CSV file.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* The CSV format is compatible with Jira/Tempo worklog imports.
|
|
28
|
+
* The file path can be absolute, relative to the project, or use `~/` for home directory.
|
|
29
|
+
*/
|
|
30
|
+
export class CsvWriter {
|
|
31
|
+
/** Plugin configuration */
|
|
32
|
+
private config: TimeTrackingConfig
|
|
33
|
+
|
|
34
|
+
/** Project directory path */
|
|
35
|
+
private directory: string
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new CSV writer instance.
|
|
39
|
+
*
|
|
40
|
+
* @param config - The plugin configuration
|
|
41
|
+
* @param directory - The project directory path
|
|
42
|
+
*/
|
|
43
|
+
constructor(config: TimeTrackingConfig, directory: string) {
|
|
44
|
+
this.config = config
|
|
45
|
+
this.directory = directory
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the CSV file path from configuration.
|
|
50
|
+
*
|
|
51
|
+
* @returns The absolute path to the CSV file
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* Handles three path formats:
|
|
55
|
+
* - `~/path` - Expands to home directory
|
|
56
|
+
* - `/absolute/path` - Used as-is
|
|
57
|
+
* - `relative/path` - Relative to project directory
|
|
58
|
+
*/
|
|
59
|
+
private resolvePath(): string {
|
|
60
|
+
let csvPath = this.config.csv_file
|
|
61
|
+
|
|
62
|
+
if (csvPath.startsWith("~/")) {
|
|
63
|
+
csvPath = csvPath.replace("~", process.env.HOME || "")
|
|
64
|
+
} else if (!csvPath.startsWith("/")) {
|
|
65
|
+
csvPath = `${this.directory}/${csvPath}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return csvPath
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Writes a time tracking entry to the CSV file.
|
|
73
|
+
*
|
|
74
|
+
* @param data - The entry data to write
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* Creates the CSV file with headers if it doesn't exist.
|
|
78
|
+
* Appends to existing file if it exists.
|
|
79
|
+
* Creates parent directories as needed.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* await csvWriter.write({
|
|
84
|
+
* ticket: "PROJ-123",
|
|
85
|
+
* startTime: Date.now() - 3600000,
|
|
86
|
+
* endTime: Date.now(),
|
|
87
|
+
* durationSeconds: 3600,
|
|
88
|
+
* description: "Implemented feature X",
|
|
89
|
+
* notes: "Auto-tracked: read(5x), edit(3x)",
|
|
90
|
+
* tokenUsage: { input: 1000, output: 500, reasoning: 0, cacheRead: 0, cacheWrite: 0 }
|
|
91
|
+
* })
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
async write(data: CsvEntryData): Promise<void> {
|
|
95
|
+
const csvPath = this.resolvePath()
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await mkdir(dirname(csvPath), { recursive: true })
|
|
99
|
+
} catch {
|
|
100
|
+
// Directory may already exist
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const file = Bun.file(csvPath)
|
|
104
|
+
const exists = await file.exists()
|
|
105
|
+
|
|
106
|
+
const totalTokens =
|
|
107
|
+
data.tokenUsage.input + data.tokenUsage.output + data.tokenUsage.reasoning
|
|
108
|
+
|
|
109
|
+
const fields = [
|
|
110
|
+
randomUUID(),
|
|
111
|
+
CsvFormatter.formatDate(data.startTime),
|
|
112
|
+
CsvFormatter.formatDate(data.endTime),
|
|
113
|
+
this.config.user_email,
|
|
114
|
+
"",
|
|
115
|
+
data.ticket ?? "",
|
|
116
|
+
this.config.default_account_key,
|
|
117
|
+
CsvFormatter.formatTime(data.startTime),
|
|
118
|
+
CsvFormatter.formatTime(data.endTime),
|
|
119
|
+
data.durationSeconds.toString(),
|
|
120
|
+
totalTokens.toString(),
|
|
121
|
+
"",
|
|
122
|
+
"",
|
|
123
|
+
CsvFormatter.escape(data.description),
|
|
124
|
+
CsvFormatter.escape(data.notes),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
const csvLine = fields.map((f) => `"${f}"`).join(",")
|
|
128
|
+
|
|
129
|
+
if (!exists) {
|
|
130
|
+
await Bun.write(csvPath, CSV_HEADER + "\n" + csvLine + "\n")
|
|
131
|
+
} else {
|
|
132
|
+
const content = await file.text()
|
|
133
|
+
await Bun.write(csvPath, content + csvLine + "\n")
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session state management for time tracking.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ActivityData } from "../types/ActivityData"
|
|
6
|
+
import type { SessionData } from "../types/SessionData"
|
|
7
|
+
import type { TokenUsage } from "../types/TokenUsage"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Manages active session state for time tracking.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Each OpenCode session is tracked separately with its own:
|
|
14
|
+
* - Start time
|
|
15
|
+
* - Ticket reference
|
|
16
|
+
* - Tool activities
|
|
17
|
+
* - Token usage statistics
|
|
18
|
+
*
|
|
19
|
+
* Sessions are stored in memory and cleaned up when completed.
|
|
20
|
+
*/
|
|
21
|
+
export class SessionManager {
|
|
22
|
+
/** Map of session ID to session data */
|
|
23
|
+
private sessions = new Map<string, SessionData>()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves session data by ID.
|
|
27
|
+
*
|
|
28
|
+
* @param sessionID - The OpenCode session identifier
|
|
29
|
+
* @returns The session data, or `undefined` if not found
|
|
30
|
+
*/
|
|
31
|
+
get(sessionID: string): SessionData | undefined {
|
|
32
|
+
return this.sessions.get(sessionID)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a session exists.
|
|
37
|
+
*
|
|
38
|
+
* @param sessionID - The OpenCode session identifier
|
|
39
|
+
* @returns `true` if the session exists, `false` otherwise
|
|
40
|
+
*/
|
|
41
|
+
has(sessionID: string): boolean {
|
|
42
|
+
return this.sessions.has(sessionID)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new session.
|
|
47
|
+
*
|
|
48
|
+
* @param sessionID - The OpenCode session identifier
|
|
49
|
+
* @param ticket - Optional Jira ticket reference (e.g., "PROJ-123")
|
|
50
|
+
* @returns The newly created session data
|
|
51
|
+
*/
|
|
52
|
+
create(sessionID: string, ticket: string | null): SessionData {
|
|
53
|
+
const session: SessionData = {
|
|
54
|
+
ticket,
|
|
55
|
+
startTime: Date.now(),
|
|
56
|
+
activities: [],
|
|
57
|
+
tokenUsage: {
|
|
58
|
+
input: 0,
|
|
59
|
+
output: 0,
|
|
60
|
+
reasoning: 0,
|
|
61
|
+
cacheRead: 0,
|
|
62
|
+
cacheWrite: 0,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.sessions.set(sessionID, session)
|
|
67
|
+
|
|
68
|
+
return session
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Deletes a session.
|
|
73
|
+
*
|
|
74
|
+
* @param sessionID - The OpenCode session identifier
|
|
75
|
+
*/
|
|
76
|
+
delete(sessionID: string): void {
|
|
77
|
+
this.sessions.delete(sessionID)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Adds a tool activity to a session.
|
|
82
|
+
*
|
|
83
|
+
* @param sessionID - The OpenCode session identifier
|
|
84
|
+
* @param activity - The activity data to add
|
|
85
|
+
*/
|
|
86
|
+
addActivity(sessionID: string, activity: ActivityData): void {
|
|
87
|
+
const session = this.sessions.get(sessionID)
|
|
88
|
+
|
|
89
|
+
if (session) {
|
|
90
|
+
session.activities.push(activity)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Adds token usage to a session's cumulative totals.
|
|
96
|
+
*
|
|
97
|
+
* @param sessionID - The OpenCode session identifier
|
|
98
|
+
* @param tokens - The token usage to add
|
|
99
|
+
*/
|
|
100
|
+
addTokenUsage(sessionID: string, tokens: TokenUsage): void {
|
|
101
|
+
const session = this.sessions.get(sessionID)
|
|
102
|
+
|
|
103
|
+
if (session) {
|
|
104
|
+
session.tokenUsage.input += tokens.input
|
|
105
|
+
session.tokenUsage.output += tokens.output
|
|
106
|
+
session.tokenUsage.reasoning += tokens.reasoning
|
|
107
|
+
session.tokenUsage.cacheRead += tokens.cacheRead
|
|
108
|
+
session.tokenUsage.cacheWrite += tokens.cacheWrite
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Updates the ticket reference for a session.
|
|
114
|
+
*
|
|
115
|
+
* @param sessionID - The OpenCode session identifier
|
|
116
|
+
* @param ticket - The new ticket reference, or `null` to keep existing
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* Only updates if a non-null ticket is provided.
|
|
120
|
+
* This allows the ticket to be updated when found in later messages.
|
|
121
|
+
*/
|
|
122
|
+
updateTicket(sessionID: string, ticket: string | null): void {
|
|
123
|
+
const session = this.sessions.get(sessionID)
|
|
124
|
+
|
|
125
|
+
if (session && ticket) {
|
|
126
|
+
session.ticket = ticket
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Extracts Jira ticket references from session context.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MessageWithParts } from "../types/MessageWithParts"
|
|
6
|
+
import type { OpencodeClient } from "../types/OpencodeClient"
|
|
7
|
+
import type { Todo } from "../types/Todo"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Regular expression pattern for Jira ticket references.
|
|
11
|
+
* Matches patterns like "PROJ-123", "ABC-1", "FEATURE-9999".
|
|
12
|
+
*/
|
|
13
|
+
const TICKET_PATTERN = /([A-Z]+-\d+)/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extracts Jira ticket references from user messages and todos.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Scans the session context for ticket patterns in this priority:
|
|
20
|
+
* 1. User messages (newest first)
|
|
21
|
+
* 2. Todo items (newest first)
|
|
22
|
+
*
|
|
23
|
+
* Returns the first match found, allowing tickets to be updated
|
|
24
|
+
* when mentioned in later messages.
|
|
25
|
+
*/
|
|
26
|
+
export class TicketExtractor {
|
|
27
|
+
/** OpenCode SDK client */
|
|
28
|
+
private client: OpencodeClient
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new ticket extractor instance.
|
|
32
|
+
*
|
|
33
|
+
* @param client - The OpenCode SDK client
|
|
34
|
+
*/
|
|
35
|
+
constructor(client: OpencodeClient) {
|
|
36
|
+
this.client = client
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extracts a ticket reference from the session context.
|
|
41
|
+
*
|
|
42
|
+
* @param sessionID - The OpenCode session identifier
|
|
43
|
+
* @returns The ticket reference (e.g., "PROJ-123"), or `null` if not found
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const ticket = await ticketExtractor.extract("session-123")
|
|
48
|
+
* // Returns "PROJ-456" if user mentioned it in a message
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
async extract(sessionID: string): Promise<string | null> {
|
|
52
|
+
const ticketFromMessages = await this.extractFromMessages(sessionID)
|
|
53
|
+
|
|
54
|
+
if (ticketFromMessages) {
|
|
55
|
+
return ticketFromMessages
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ticketFromTodos = await this.extractFromTodos(sessionID)
|
|
59
|
+
|
|
60
|
+
return ticketFromTodos
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extracts a ticket from user messages.
|
|
65
|
+
*
|
|
66
|
+
* @param sessionID - The OpenCode session identifier
|
|
67
|
+
* @returns The ticket reference, or `null` if not found
|
|
68
|
+
*/
|
|
69
|
+
private async extractFromMessages(
|
|
70
|
+
sessionID: string
|
|
71
|
+
): Promise<string | null> {
|
|
72
|
+
try {
|
|
73
|
+
const result = await this.client.session.messages({
|
|
74
|
+
path: { id: sessionID },
|
|
75
|
+
} as Parameters<typeof this.client.session.messages>[0])
|
|
76
|
+
|
|
77
|
+
if (!result.data) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const messages = result.data as MessageWithParts[]
|
|
82
|
+
|
|
83
|
+
// Scan user messages for ticket pattern (newest first)
|
|
84
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
85
|
+
const message = messages[i]
|
|
86
|
+
|
|
87
|
+
if (message.info.role !== "user") {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const part of message.parts) {
|
|
92
|
+
if (part.type === "text" && part.text) {
|
|
93
|
+
const ticket = this.extractFromText(part.text)
|
|
94
|
+
|
|
95
|
+
if (ticket) {
|
|
96
|
+
return ticket
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null
|
|
103
|
+
} catch {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extracts a ticket from todo items.
|
|
110
|
+
*
|
|
111
|
+
* @param sessionID - The OpenCode session identifier
|
|
112
|
+
* @returns The ticket reference, or `null` if not found
|
|
113
|
+
*/
|
|
114
|
+
private async extractFromTodos(sessionID: string): Promise<string | null> {
|
|
115
|
+
try {
|
|
116
|
+
const result = await this.client.session.todo({
|
|
117
|
+
path: { id: sessionID },
|
|
118
|
+
} as Parameters<typeof this.client.session.todo>[0])
|
|
119
|
+
|
|
120
|
+
if (!result.data) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const todos = result.data as Todo[]
|
|
125
|
+
|
|
126
|
+
// Scan todos for ticket pattern (newest first)
|
|
127
|
+
for (let i = todos.length - 1; i >= 0; i--) {
|
|
128
|
+
const todo = todos[i]
|
|
129
|
+
|
|
130
|
+
if (todo.content) {
|
|
131
|
+
const ticket = this.extractFromText(todo.content)
|
|
132
|
+
|
|
133
|
+
if (ticket) {
|
|
134
|
+
return ticket
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null
|
|
140
|
+
} catch {
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extracts a ticket pattern from text.
|
|
147
|
+
*
|
|
148
|
+
* @param text - The text to search
|
|
149
|
+
* @returns The first ticket match, or `null` if not found
|
|
150
|
+
*/
|
|
151
|
+
private extractFromText(text: string): string | null {
|
|
152
|
+
const match = text.match(TICKET_PATTERN)
|
|
153
|
+
|
|
154
|
+
return match?.[1] ?? null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Activity data type for tracking tool executions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a single tool activity within a session.
|
|
7
|
+
*/
|
|
8
|
+
export interface ActivityData {
|
|
9
|
+
/** The name of the tool that was executed (e.g., "read", "edit", "bash") */
|
|
10
|
+
tool: string
|
|
11
|
+
|
|
12
|
+
/** Unix timestamp in milliseconds when the activity occurred */
|
|
13
|
+
timestamp: number
|
|
14
|
+
|
|
15
|
+
/** Optional file path associated with the activity */
|
|
16
|
+
file?: string
|
|
17
|
+
}
|
package/src/types/Bun.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Global Bun type declarations.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This file declares the Bun global object for TypeScript.
|
|
6
|
+
* The actual Bun runtime is provided by OpenCode at plugin load time.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
/** Bun runtime file system API */
|
|
11
|
+
const Bun: {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a file handle for the given path.
|
|
14
|
+
*
|
|
15
|
+
* @param path - The file path
|
|
16
|
+
* @returns A file handle object
|
|
17
|
+
*/
|
|
18
|
+
file(path: string): {
|
|
19
|
+
/** Checks if the file exists */
|
|
20
|
+
exists(): Promise<boolean>
|
|
21
|
+
|
|
22
|
+
/** Parses the file as JSON */
|
|
23
|
+
json(): Promise<unknown>
|
|
24
|
+
|
|
25
|
+
/** Reads the file as text */
|
|
26
|
+
text(): Promise<string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Writes content to a file.
|
|
31
|
+
*
|
|
32
|
+
* @param path - The file path
|
|
33
|
+
* @param content - The content to write
|
|
34
|
+
* @returns The number of bytes written
|
|
35
|
+
*/
|
|
36
|
+
write(path: string, content: string): Promise<number>
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CSV entry data type for worklog exports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TokenUsage } from "./TokenUsage"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Data structure for a single CSV worklog entry.
|
|
9
|
+
*/
|
|
10
|
+
export interface CsvEntryData {
|
|
11
|
+
/** Jira ticket reference (e.g., "PROJ-123"), or `null` if not found */
|
|
12
|
+
ticket: string | null
|
|
13
|
+
|
|
14
|
+
/** Session start time as Unix timestamp in milliseconds */
|
|
15
|
+
startTime: number
|
|
16
|
+
|
|
17
|
+
/** Session end time as Unix timestamp in milliseconds */
|
|
18
|
+
endTime: number
|
|
19
|
+
|
|
20
|
+
/** Duration of the session in seconds */
|
|
21
|
+
durationSeconds: number
|
|
22
|
+
|
|
23
|
+
/** Human-readable description of the work performed */
|
|
24
|
+
description: string
|
|
25
|
+
|
|
26
|
+
/** Additional notes (e.g., tool usage summary) */
|
|
27
|
+
notes: string
|
|
28
|
+
|
|
29
|
+
/** Token consumption statistics */
|
|
30
|
+
tokenUsage: TokenUsage
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message info type from OpenCode SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MessageSummary } from "./MessageSummary"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Information about a message in the conversation.
|
|
9
|
+
*/
|
|
10
|
+
export interface MessageInfo {
|
|
11
|
+
/** The role of the message sender */
|
|
12
|
+
role: "user" | "assistant"
|
|
13
|
+
|
|
14
|
+
/** Optional summary generated by the LLM */
|
|
15
|
+
summary?: MessageSummary
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message part type from OpenCode SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A part of a message (text, file, tool call, etc.).
|
|
7
|
+
*/
|
|
8
|
+
export interface MessagePart {
|
|
9
|
+
/** The type of the part (e.g., "text", "tool", "file") */
|
|
10
|
+
type: string
|
|
11
|
+
|
|
12
|
+
/** Text content for text parts */
|
|
13
|
+
text?: string
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Properties for message.part.updated events.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { StepFinishPart } from "./StepFinishPart"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Properties received with message.part.updated events.
|
|
9
|
+
*/
|
|
10
|
+
export interface MessagePartUpdatedProperties {
|
|
11
|
+
/** The updated message part */
|
|
12
|
+
part: {
|
|
13
|
+
/** The type of the part */
|
|
14
|
+
type: string
|
|
15
|
+
|
|
16
|
+
/** Session ID (present on step-finish parts) */
|
|
17
|
+
sessionID?: string
|
|
18
|
+
|
|
19
|
+
/** Token usage (present on step-finish parts) */
|
|
20
|
+
tokens?: StepFinishPart["tokens"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message summary type from OpenCode SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LLM-generated summary of a message's changes.
|
|
7
|
+
*/
|
|
8
|
+
export interface MessageSummary {
|
|
9
|
+
/** Short title describing the changes */
|
|
10
|
+
title?: string
|
|
11
|
+
|
|
12
|
+
/** Longer description of the changes */
|
|
13
|
+
body?: string
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Message with parts type from OpenCode SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MessageInfo } from "./MessageInfo"
|
|
6
|
+
import type { MessagePart } from "./MessagePart"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A message with its associated parts.
|
|
10
|
+
*/
|
|
11
|
+
export interface MessageWithParts {
|
|
12
|
+
/** Message metadata */
|
|
13
|
+
info: MessageInfo
|
|
14
|
+
|
|
15
|
+
/** Array of message parts (text, tools, files, etc.) */
|
|
16
|
+
parts: MessagePart[]
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview OpenCode SDK client type.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { createOpencodeClient } from "@opencode-ai/sdk"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The OpenCode SDK client type.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Derived from the return type of `createOpencodeClient`.
|
|
12
|
+
* Provides access to session, TUI, and other OpenCode APIs.
|
|
13
|
+
*/
|
|
14
|
+
export type OpencodeClient = ReturnType<typeof createOpencodeClient>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Session data type for time tracking state.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ActivityData } from "./ActivityData"
|
|
6
|
+
import type { TokenUsage } from "./TokenUsage"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* State data for a tracked session.
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionData {
|
|
12
|
+
/** Jira ticket reference, or `null` if not found */
|
|
13
|
+
ticket: string | null
|
|
14
|
+
|
|
15
|
+
/** Session start time as Unix timestamp in milliseconds */
|
|
16
|
+
startTime: number
|
|
17
|
+
|
|
18
|
+
/** Array of tool activities recorded during the session */
|
|
19
|
+
activities: ActivityData[]
|
|
20
|
+
|
|
21
|
+
/** Cumulative token usage for the session */
|
|
22
|
+
tokenUsage: TokenUsage
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Step finish part type from OpenCode SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A step-finish message part containing token usage.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Emitted when a model completes a reasoning step.
|
|
10
|
+
* Contains detailed token consumption statistics.
|
|
11
|
+
*/
|
|
12
|
+
export interface StepFinishPart {
|
|
13
|
+
/** Part type identifier */
|
|
14
|
+
type: "step-finish"
|
|
15
|
+
|
|
16
|
+
/** The session this part belongs to */
|
|
17
|
+
sessionID: string
|
|
18
|
+
|
|
19
|
+
/** Token usage for this step */
|
|
20
|
+
tokens: {
|
|
21
|
+
/** Input tokens consumed */
|
|
22
|
+
input: number
|
|
23
|
+
|
|
24
|
+
/** Output tokens generated */
|
|
25
|
+
output: number
|
|
26
|
+
|
|
27
|
+
/** Reasoning tokens used (for o1-style models) */
|
|
28
|
+
reasoning: number
|
|
29
|
+
|
|
30
|
+
/** Cache statistics */
|
|
31
|
+
cache: {
|
|
32
|
+
/** Tokens read from cache */
|
|
33
|
+
read: number
|
|
34
|
+
|
|
35
|
+
/** Tokens written to cache */
|
|
36
|
+
write: number
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|