@wuyoumaster/opencode-telemetry-panel 0.0.7
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 +72 -0
- package/README_CN.md +72 -0
- package/package.json +53 -0
- package/plugin/opencode-telemetry-panel.ts +214 -0
- package/scripts/postinstall.mjs +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# OpenCode Telemetry Panel
|
|
2
|
+
|
|
3
|
+
OpenCode telemetry plugin plus a Tauri floating panel.
|
|
4
|
+
|
|
5
|
+
中文文档: [README_CN.md](./README_CN.md)
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Captures request telemetry from OpenCode events.
|
|
10
|
+
- Stores telemetry in `~/.opencode-telemetry/telemetry.jsonl`.
|
|
11
|
+
- Launches a native floating panel from the plugin.
|
|
12
|
+
- Downloads the matching executable automatically during `postinstall`.
|
|
13
|
+
|
|
14
|
+
## Package Layout
|
|
15
|
+
|
|
16
|
+
- `plugin/opencode-telemetry-panel.ts` OpenCode plugin entry.
|
|
17
|
+
- `src/` Solid panel UI.
|
|
18
|
+
- `src-tauri/` Tauri backend.
|
|
19
|
+
- `scripts/postinstall.mjs` binary downloader.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @opencode-ai/telemetry-panel
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm i @opencode-ai/telemetry-panel
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The install step runs `postinstall` and downloads the matching binary into `~/.opencode-telemetry/`.
|
|
34
|
+
|
|
35
|
+
## Environment Variables
|
|
36
|
+
|
|
37
|
+
- `OPENCODE_TELEMETRY_PANEL_REPO` overrides the GitHub repo slug used for binary downloads.
|
|
38
|
+
- `OPENCODE_TELEMETRY_PANEL_BIN` forces a custom binary path.
|
|
39
|
+
- `OPENCODE_TELEMETRY_PANEL_SKIP_DOWNLOAD=1` disables binary download during CI installs.
|
|
40
|
+
|
|
41
|
+
## Supported Platforms
|
|
42
|
+
|
|
43
|
+
- Windows x64
|
|
44
|
+
- macOS x64
|
|
45
|
+
- macOS arm64
|
|
46
|
+
|
|
47
|
+
Linux is intentionally not supported for release artifacts yet.
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun install
|
|
53
|
+
bun run tauri dev
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Build Executable
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bun run build:exe
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This outputs the executable only, not an installer.
|
|
63
|
+
|
|
64
|
+
## Release
|
|
65
|
+
|
|
66
|
+
- Push a `v*` tag to publish npm and GitHub Release artifacts.
|
|
67
|
+
- GitHub Releases contain the platform-specific executables.
|
|
68
|
+
|
|
69
|
+
## Notes
|
|
70
|
+
|
|
71
|
+
- The plugin reads the binary from `~/.opencode-telemetry/OpenCodeTelemetryPanel(.exe)` by default.
|
|
72
|
+
- On Windows, the binary is `OpenCodeTelemetryPanel.exe`.
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# OpenCode Telemetry Panel
|
|
2
|
+
|
|
3
|
+
OpenCode 监控插件 + Tauri 浮窗面板。
|
|
4
|
+
|
|
5
|
+
English: [README.md](./README.md)
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
- 监听 OpenCode 事件并记录请求指标。
|
|
10
|
+
- 将 telemetry 保存到 `~/.opencode-telemetry/telemetry.jsonl`。
|
|
11
|
+
- 由插件拉起本地浮窗面板。
|
|
12
|
+
- 安装时通过 `postinstall` 自动下载匹配平台的可执行文件。
|
|
13
|
+
|
|
14
|
+
## 仓库结构
|
|
15
|
+
|
|
16
|
+
- `plugin/opencode-telemetry-panel.ts` OpenCode 插件入口。
|
|
17
|
+
- `src/` Solid 前端界面。
|
|
18
|
+
- `src-tauri/` Tauri 后端。
|
|
19
|
+
- `scripts/postinstall.mjs` 二进制下载脚本。
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @opencode-ai/telemetry-panel
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
或者
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm i @opencode-ai/telemetry-panel
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
安装时会执行 `postinstall`,把对应平台的可执行文件下载到 `~/.opencode-telemetry/`。
|
|
34
|
+
|
|
35
|
+
## 环境变量
|
|
36
|
+
|
|
37
|
+
- `OPENCODE_TELEMETRY_PANEL_REPO` 覆盖下载二进制时使用的 GitHub 仓库地址。
|
|
38
|
+
- `OPENCODE_TELEMETRY_PANEL_BIN` 手动指定可执行文件路径。
|
|
39
|
+
- `OPENCODE_TELEMETRY_PANEL_SKIP_DOWNLOAD=1` 可在 CI 安装时跳过二进制下载。
|
|
40
|
+
|
|
41
|
+
## 支持平台
|
|
42
|
+
|
|
43
|
+
- Windows x64
|
|
44
|
+
- macOS x64
|
|
45
|
+
- macOS arm64
|
|
46
|
+
|
|
47
|
+
暂时不发布 Linux 构建产物。
|
|
48
|
+
|
|
49
|
+
## 开发
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun install
|
|
53
|
+
bun run tauri dev
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 构建可执行文件
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bun run build:exe
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
这里只输出可执行文件,不生成安装包。
|
|
63
|
+
|
|
64
|
+
## 发布
|
|
65
|
+
|
|
66
|
+
- 推送 `v*` tag 会同时触发 npm 发布和 GitHub Release。
|
|
67
|
+
- GitHub Release 会附带各平台的可执行文件。
|
|
68
|
+
|
|
69
|
+
## 说明
|
|
70
|
+
|
|
71
|
+
- 插件默认从 `~/.opencode-telemetry/OpenCodeTelemetryPanel(.exe)` 读取可执行文件。
|
|
72
|
+
- Windows 下文件名是 `OpenCodeTelemetryPanel.exe`。
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@wuyoumaster/opencode-telemetry-panel",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "OpenCode telemetry panel plugin with a companion Tauri binary",
|
|
6
|
+
"version": "0.0.7",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"packageManager": "bun@1.3.11",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/wuyouMaster/opencode-telemetry-panel.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/wuyouMaster/opencode-telemetry-panel",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/wuyouMaster/opencode-telemetry-panel/issues"
|
|
16
|
+
},
|
|
17
|
+
"main": "./plugin/opencode-telemetry-panel.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./plugin/opencode-telemetry-panel.ts",
|
|
20
|
+
"./server": "./plugin/opencode-telemetry-panel.ts"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"plugin/opencode-telemetry-panel.ts",
|
|
24
|
+
"scripts/postinstall.mjs",
|
|
25
|
+
"README.md",
|
|
26
|
+
"README_CN.md"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "vite",
|
|
33
|
+
"build": "vite build",
|
|
34
|
+
"build:exe": "tauri build --no-bundle",
|
|
35
|
+
"postinstall": "node ./scripts/postinstall.mjs",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"tauri": "tauri"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@tauri-apps/api": "^2",
|
|
44
|
+
"solid-js": "1.9.10"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@tauri-apps/cli": "^2",
|
|
48
|
+
"@types/bun": "1.3.11",
|
|
49
|
+
"typescript": "5.8.2",
|
|
50
|
+
"vite": "7.1.4",
|
|
51
|
+
"vite-plugin-solid": "2.11.10"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { dirname, join } from "node:path"
|
|
4
|
+
import { homedir } from "node:os"
|
|
5
|
+
|
|
6
|
+
type SessionStatusEvent = {
|
|
7
|
+
type: "session.status"
|
|
8
|
+
properties: {
|
|
9
|
+
sessionID: string
|
|
10
|
+
status: {
|
|
11
|
+
type: string
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type MessagePartDeltaEvent = {
|
|
17
|
+
type: "message.part.delta"
|
|
18
|
+
properties: {
|
|
19
|
+
sessionID: string
|
|
20
|
+
messageID: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type MessageUpdatedEvent = {
|
|
25
|
+
type: "message.updated"
|
|
26
|
+
properties: {
|
|
27
|
+
sessionID: string
|
|
28
|
+
info: {
|
|
29
|
+
role: string
|
|
30
|
+
id: string
|
|
31
|
+
providerID: string
|
|
32
|
+
modelID: string
|
|
33
|
+
time: {
|
|
34
|
+
created: number
|
|
35
|
+
completed?: number | null
|
|
36
|
+
error?: unknown
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type SessionErrorEvent = {
|
|
43
|
+
type: "session.error"
|
|
44
|
+
properties: {
|
|
45
|
+
sessionID?: string | null
|
|
46
|
+
error: unknown
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type TelemetryEvent = SessionStatusEvent | MessagePartDeltaEvent | MessageUpdatedEvent | SessionErrorEvent
|
|
51
|
+
|
|
52
|
+
type Plugin = () => Promise<{
|
|
53
|
+
event: (input: { event: TelemetryEvent }) => Promise<void>
|
|
54
|
+
}>
|
|
55
|
+
|
|
56
|
+
type PendingRequest = {
|
|
57
|
+
sessionId: string
|
|
58
|
+
messageId: string
|
|
59
|
+
providerId: string
|
|
60
|
+
modelId: string
|
|
61
|
+
startedAt: number
|
|
62
|
+
firstTokenAt?: number
|
|
63
|
+
retries: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const root = join(homedir(), ".opencode-telemetry")
|
|
67
|
+
const file = join(root, "telemetry.jsonl")
|
|
68
|
+
const executablePath = join(
|
|
69
|
+
root,
|
|
70
|
+
process.platform === "win32" ? "OpenCodeTelemetryPanel.exe" : "OpenCodeTelemetryPanel",
|
|
71
|
+
)
|
|
72
|
+
const pending = new Map<string, PendingRequest>()
|
|
73
|
+
const firstTokenAt = new Map<string, number>()
|
|
74
|
+
let panelLaunched = false
|
|
75
|
+
|
|
76
|
+
function key(sessionId: string, messageId: string) {
|
|
77
|
+
return `${sessionId}:${messageId}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function compactError(error: unknown) {
|
|
81
|
+
if (!error) return "session error"
|
|
82
|
+
if (typeof error !== "object") return String(error)
|
|
83
|
+
if ("data" in error && error.data && typeof error.data === "object" && "message" in error.data) {
|
|
84
|
+
return String((error.data as { message?: unknown }).message ?? (error as { message?: unknown }).message ?? "")
|
|
85
|
+
}
|
|
86
|
+
return String((error as { message?: unknown }).message ?? error)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function append(record: Record<string, unknown>) {
|
|
90
|
+
await mkdir(dirname(file), { recursive: true })
|
|
91
|
+
await appendFile(file, `${JSON.stringify(record)}\n`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function launchPanel() {
|
|
95
|
+
if (panelLaunched) return
|
|
96
|
+
|
|
97
|
+
const executable = [process.env.OPENCODE_TELEMETRY_PANEL_BIN, executablePath]
|
|
98
|
+
.filter((value): value is string => typeof value === "string")
|
|
99
|
+
.find((value) => existsSync(value))
|
|
100
|
+
|
|
101
|
+
if (!executable) return
|
|
102
|
+
|
|
103
|
+
const child = Bun.spawn([executable], {
|
|
104
|
+
detached: true,
|
|
105
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
child.unref()
|
|
109
|
+
panelLaunched = true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function recordFromPending(pendingRequest: PendingRequest, finishedAt: number, success: boolean, error?: string) {
|
|
113
|
+
const firstTokenAt = pendingRequest.firstTokenAt ?? finishedAt
|
|
114
|
+
return {
|
|
115
|
+
kind: "request",
|
|
116
|
+
sessionId: pendingRequest.sessionId,
|
|
117
|
+
messageId: pendingRequest.messageId,
|
|
118
|
+
providerId: pendingRequest.providerId,
|
|
119
|
+
modelId: pendingRequest.modelId,
|
|
120
|
+
startedAt: pendingRequest.startedAt,
|
|
121
|
+
firstTokenAt,
|
|
122
|
+
completedAt: finishedAt,
|
|
123
|
+
success,
|
|
124
|
+
error: error ?? null,
|
|
125
|
+
retries: pendingRequest.retries,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const TelemetryPanelPlugin: Plugin = async () => {
|
|
130
|
+
launchPanel()
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
event: async ({ event }) => {
|
|
134
|
+
if (event.type === "session.status") {
|
|
135
|
+
if (event.properties.status.type === "idle") {
|
|
136
|
+
for (const [requestKey, request] of pending.entries()) {
|
|
137
|
+
if (request.sessionId !== event.properties.sessionID) continue
|
|
138
|
+
pending.delete(requestKey)
|
|
139
|
+
firstTokenAt.delete(requestKey)
|
|
140
|
+
}
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (event.properties.status.type === "retry") {
|
|
145
|
+
for (const request of pending.values()) {
|
|
146
|
+
if (request.sessionId === event.properties.sessionID) request.retries += 1
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (event.type === "message.part.delta") {
|
|
154
|
+
const requestKey = key(event.properties.sessionID, event.properties.messageID)
|
|
155
|
+
if (!firstTokenAt.has(requestKey)) firstTokenAt.set(requestKey, Date.now())
|
|
156
|
+
|
|
157
|
+
const request = pending.get(requestKey)
|
|
158
|
+
if (request && request.firstTokenAt === undefined) request.firstTokenAt = firstTokenAt.get(requestKey)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (event.type === "message.updated") {
|
|
163
|
+
if (event.properties.info.role !== "assistant") return
|
|
164
|
+
|
|
165
|
+
const requestKey = key(event.properties.sessionID, event.properties.info.id)
|
|
166
|
+
const existing = pending.get(requestKey)
|
|
167
|
+
const next =
|
|
168
|
+
existing ??
|
|
169
|
+
({
|
|
170
|
+
sessionId: event.properties.sessionID,
|
|
171
|
+
messageId: event.properties.info.id,
|
|
172
|
+
providerId: event.properties.info.providerID,
|
|
173
|
+
modelId: event.properties.info.modelID,
|
|
174
|
+
startedAt: event.properties.info.time.created,
|
|
175
|
+
retries: 0,
|
|
176
|
+
} satisfies PendingRequest)
|
|
177
|
+
|
|
178
|
+
if (next.firstTokenAt === undefined) next.firstTokenAt = firstTokenAt.get(requestKey)
|
|
179
|
+
|
|
180
|
+
if (!existing) pending.set(requestKey, next)
|
|
181
|
+
|
|
182
|
+
if (!event.properties.info.time.completed) return
|
|
183
|
+
|
|
184
|
+
await append(
|
|
185
|
+
recordFromPending(
|
|
186
|
+
next,
|
|
187
|
+
event.properties.info.time.completed,
|
|
188
|
+
!event.properties.info.time.error,
|
|
189
|
+
event.properties.info.time.error ? compactError(event.properties.info.time.error) : undefined,
|
|
190
|
+
),
|
|
191
|
+
).catch(() => undefined)
|
|
192
|
+
pending.delete(requestKey)
|
|
193
|
+
firstTokenAt.delete(requestKey)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (event.type === "session.error") {
|
|
198
|
+
const sessionId = event.properties.sessionID
|
|
199
|
+
if (!sessionId) return
|
|
200
|
+
|
|
201
|
+
const requests = [...pending.values()].filter((request) => request.sessionId === sessionId)
|
|
202
|
+
const request = requests.sort((left, right) => right.startedAt - left.startedAt)[0]
|
|
203
|
+
if (!request) return
|
|
204
|
+
|
|
205
|
+
await append(recordFromPending(request, Date.now(), false, compactError(event.properties.error))).catch(
|
|
206
|
+
() => undefined,
|
|
207
|
+
)
|
|
208
|
+
const requestKey = key(request.sessionId, request.messageId)
|
|
209
|
+
pending.delete(requestKey)
|
|
210
|
+
firstTokenAt.delete(requestKey)
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"))
|
|
7
|
+
const root = join(homedir(), ".opencode-telemetry")
|
|
8
|
+
const binaryName = process.platform === "win32" ? "OpenCodeTelemetryPanel.exe" : "OpenCodeTelemetryPanel"
|
|
9
|
+
const binaryPath = join(root, binaryName)
|
|
10
|
+
const version = packageJson.version
|
|
11
|
+
const repo = resolveRepo(packageJson)
|
|
12
|
+
const asset = resolveAsset()
|
|
13
|
+
|
|
14
|
+
if (process.env.OPENCODE_TELEMETRY_PANEL_SKIP_DOWNLOAD === "1") {
|
|
15
|
+
process.exit(0)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!asset) {
|
|
19
|
+
console.warn("[opencode-telemetry-panel] skipping binary download on unsupported platform")
|
|
20
|
+
process.exit(0)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!repo) {
|
|
24
|
+
console.warn("[opencode-telemetry-panel] skipping binary download because repository is not configured")
|
|
25
|
+
process.exit(0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await mkdir(root, { recursive: true })
|
|
29
|
+
|
|
30
|
+
const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`
|
|
31
|
+
const response = await fetch(url)
|
|
32
|
+
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
if (response.status === 404) {
|
|
35
|
+
console.warn(`[opencode-telemetry-panel] binary not found at ${url}; skipping download`)
|
|
36
|
+
process.exit(0)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error(`failed to download telemetry panel binary from ${url}: ${response.status} ${response.statusText}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tempPath = `${binaryPath}.download`
|
|
43
|
+
await writeFile(tempPath, Buffer.from(await response.arrayBuffer()))
|
|
44
|
+
|
|
45
|
+
if (process.platform !== "win32") await chmod(tempPath, 0o755)
|
|
46
|
+
|
|
47
|
+
if (existsSync(binaryPath)) await rm(binaryPath, { force: true })
|
|
48
|
+
await rename(tempPath, binaryPath)
|
|
49
|
+
|
|
50
|
+
function resolveRepo(pkg) {
|
|
51
|
+
const override = process.env.OPENCODE_TELEMETRY_PANEL_REPO?.trim()
|
|
52
|
+
if (override) return parseRepo(override) ?? override.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "")
|
|
53
|
+
|
|
54
|
+
const value = pkg.repository
|
|
55
|
+
if (typeof value === "string") return parseRepo(value)
|
|
56
|
+
if (value && typeof value.url === "string") return parseRepo(value.url)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseRepo(value) {
|
|
60
|
+
const text = value.trim()
|
|
61
|
+
if (!text) return
|
|
62
|
+
const https = text.match(/github\.com[:/](.+?)(?:\.git)?$/)
|
|
63
|
+
if (https) return https[1]
|
|
64
|
+
|
|
65
|
+
const git = text.match(/^git@github\.com:(.+?)(?:\.git)?$/)
|
|
66
|
+
if (git) return git[1]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveAsset() {
|
|
70
|
+
if (process.platform === "win32" && process.arch === "x64") return "opencode-telemetry-panel-windows-x64.exe"
|
|
71
|
+
if (process.platform === "darwin" && process.arch === "arm64") return "opencode-telemetry-panel-macos-arm64"
|
|
72
|
+
if (process.platform === "darwin" && process.arch === "x64") return "opencode-telemetry-panel-macos-x64"
|
|
73
|
+
}
|