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,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Configuration type for the time tracking plugin.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin configuration loaded from `.opencode/time-tracking.json`.
|
|
7
|
+
*/
|
|
8
|
+
export interface TimeTrackingConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Path to the CSV output file.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Supports three formats:
|
|
14
|
+
* - `~/path` - Expands to home directory
|
|
15
|
+
* - `/absolute/path` - Used as-is
|
|
16
|
+
* - `relative/path` - Relative to project directory
|
|
17
|
+
*/
|
|
18
|
+
csv_file: string
|
|
19
|
+
|
|
20
|
+
/** Email address of the user for the worklog */
|
|
21
|
+
user_email: string
|
|
22
|
+
|
|
23
|
+
/** Default Jira account key for time entries */
|
|
24
|
+
default_account_key: string
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Token usage statistics type.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Token consumption statistics for a session.
|
|
7
|
+
*/
|
|
8
|
+
export interface TokenUsage {
|
|
9
|
+
/** Number of input tokens consumed */
|
|
10
|
+
input: number
|
|
11
|
+
|
|
12
|
+
/** Number of output tokens generated */
|
|
13
|
+
output: number
|
|
14
|
+
|
|
15
|
+
/** Number of reasoning tokens used (for o1-style models) */
|
|
16
|
+
reasoning: number
|
|
17
|
+
|
|
18
|
+
/** Number of tokens read from cache */
|
|
19
|
+
cacheRead: number
|
|
20
|
+
|
|
21
|
+
/** Number of tokens written to cache */
|
|
22
|
+
cacheWrite: number
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Input type for tool.execute.after hook.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Input received by the tool.execute.after hook.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolExecuteAfterInput {
|
|
9
|
+
/** Name of the tool that was executed */
|
|
10
|
+
tool: string
|
|
11
|
+
|
|
12
|
+
/** The OpenCode session identifier */
|
|
13
|
+
sessionID: string
|
|
14
|
+
|
|
15
|
+
/** Unique identifier for this tool call */
|
|
16
|
+
callID: string
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Output type for tool.execute.after hook.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Output data from the tool.execute.after hook.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolExecuteAfterOutput {
|
|
9
|
+
/** Display title for the tool execution */
|
|
10
|
+
title: string
|
|
11
|
+
|
|
12
|
+
/** Tool output content */
|
|
13
|
+
output: string
|
|
14
|
+
|
|
15
|
+
/** Tool-specific metadata (varies by tool type) */
|
|
16
|
+
metadata: unknown
|
|
17
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CSV formatting utilities.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility class for CSV formatting operations.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Provides static methods for escaping and formatting values
|
|
10
|
+
* according to CSV standards.
|
|
11
|
+
*/
|
|
12
|
+
export class CsvFormatter {
|
|
13
|
+
/**
|
|
14
|
+
* Escapes a string value for CSV output.
|
|
15
|
+
*
|
|
16
|
+
* @param value - The string to escape
|
|
17
|
+
* @returns The escaped string with double quotes doubled
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* CsvFormatter.escape('Say "Hello"') // Returns: Say ""Hello""
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
static escape(value: string): string {
|
|
25
|
+
return value.replace(/"/g, '""')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats a timestamp as an ISO date string (YYYY-MM-DD).
|
|
30
|
+
*
|
|
31
|
+
* @param timestamp - The Unix timestamp in milliseconds
|
|
32
|
+
* @returns The formatted date string
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* CsvFormatter.formatDate(1704067200000) // Returns: "2024-01-01"
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
static formatDate(timestamp: number): string {
|
|
40
|
+
return new Date(timestamp).toISOString().split("T")[0]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Formats a timestamp as a time string (HH:MM:SS).
|
|
45
|
+
*
|
|
46
|
+
* @param timestamp - The Unix timestamp in milliseconds
|
|
47
|
+
* @returns The formatted time string in local time
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* CsvFormatter.formatTime(1704067200000) // Returns: "12:00:00"
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
static formatTime(timestamp: number): string {
|
|
55
|
+
return new Date(timestamp).toTimeString().split(" ")[0]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Generates human-readable descriptions from activity data.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ActivityData } from "../types/ActivityData"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates human-readable descriptions from tool activities.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Creates descriptions summarizing what tools were used and which files
|
|
12
|
+
* were affected during a session.
|
|
13
|
+
*/
|
|
14
|
+
export class DescriptionGenerator {
|
|
15
|
+
/**
|
|
16
|
+
* Generates a human-readable description of activities.
|
|
17
|
+
*
|
|
18
|
+
* @param activities - Array of activity data from the session
|
|
19
|
+
* @returns A formatted description string
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Groups activities by type:
|
|
23
|
+
* - File edits (edit + write tools)
|
|
24
|
+
* - File reads
|
|
25
|
+
* - Commands (bash)
|
|
26
|
+
* - Searches (glob + grep)
|
|
27
|
+
*
|
|
28
|
+
* Also includes file names if 5 or fewer files were touched.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const desc = DescriptionGenerator.generate(activities)
|
|
33
|
+
* // Returns: "3 file edit(s), 2 file read(s) - Files: index.ts, utils.ts"
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
static generate(activities: ActivityData[]): string {
|
|
37
|
+
if (activities.length === 0) {
|
|
38
|
+
return "No activities tracked"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const toolCounts = activities.reduce(
|
|
42
|
+
(acc, a) => {
|
|
43
|
+
acc[a.tool] = (acc[a.tool] || 0) + 1
|
|
44
|
+
return acc
|
|
45
|
+
},
|
|
46
|
+
{} as Record<string, number>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const filesWorkedOn = new Set<string>()
|
|
50
|
+
|
|
51
|
+
for (const activity of activities) {
|
|
52
|
+
if (activity.file) {
|
|
53
|
+
const fileName = activity.file.split("/").pop() || activity.file
|
|
54
|
+
filesWorkedOn.add(fileName)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const mainActivities: string[] = []
|
|
59
|
+
|
|
60
|
+
if (toolCounts.edit || toolCounts.write) {
|
|
61
|
+
mainActivities.push(
|
|
62
|
+
`${(toolCounts.edit || 0) + (toolCounts.write || 0)} file edit(s)`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (toolCounts.read) {
|
|
67
|
+
mainActivities.push(`${toolCounts.read} file read(s)`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (toolCounts.bash) {
|
|
71
|
+
mainActivities.push(`${toolCounts.bash} command(s)`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (toolCounts.glob || toolCounts.grep) {
|
|
75
|
+
mainActivities.push(
|
|
76
|
+
`${(toolCounts.glob || 0) + (toolCounts.grep || 0)} search(es)`
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let description =
|
|
81
|
+
mainActivities.length > 0
|
|
82
|
+
? mainActivities.join(", ")
|
|
83
|
+
: `${activities.length} tool call(s)`
|
|
84
|
+
|
|
85
|
+
if (filesWorkedOn.size > 0 && filesWorkedOn.size <= 5) {
|
|
86
|
+
description += ` - Files: ${Array.from(filesWorkedOn).join(", ")}`
|
|
87
|
+
} else if (filesWorkedOn.size > 5) {
|
|
88
|
+
description += ` - ${filesWorkedOn.size} files`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return description
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generates a compact tool usage summary.
|
|
96
|
+
*
|
|
97
|
+
* @param activities - Array of activity data from the session
|
|
98
|
+
* @returns A compact summary string showing tool counts
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const summary = DescriptionGenerator.generateToolSummary(activities)
|
|
103
|
+
* // Returns: "read(5x), edit(3x), bash(2x)"
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
static generateToolSummary(activities: ActivityData[]): string {
|
|
107
|
+
const toolCounts = activities.reduce(
|
|
108
|
+
(acc, a) => {
|
|
109
|
+
acc[a.tool] = (acc[a.tool] || 0) + 1
|
|
110
|
+
return acc
|
|
111
|
+
},
|
|
112
|
+
{} as Record<string, number>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return Object.entries(toolCounts)
|
|
116
|
+
.map(([t, c]) => `${t}(${c}x)`)
|
|
117
|
+
.join(", ")
|
|
118
|
+
}
|
|
119
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["bun-types", "node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|