backeer-opencode 0.1.4 → 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/src/updates.js ADDED
@@ -0,0 +1,136 @@
1
+ export const PLUGIN_VERSION = "0.2.0"
2
+
3
+ const PACKAGE_NAME = "backeer-opencode"
4
+ const NPM_REGISTRY_URL = "https://registry.npmjs.org"
5
+ const CHECK_TIMEOUT_MS = 3000
6
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
7
+
8
+ export function parseVersion(value) {
9
+ const match = String(value).trim().match(
10
+ /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/,
11
+ )
12
+ if (!match) return
13
+ return {
14
+ parts: [Number(match[1]), Number(match[2]), Number(match[3])],
15
+ prerelease: match[4],
16
+ }
17
+ }
18
+
19
+ export function isVersionNewer(latestValue, currentValue) {
20
+ const latest = parseVersion(latestValue)
21
+ const current = parseVersion(currentValue)
22
+ if (!latest || !current) return false
23
+
24
+ for (let index = 0; index < 3; index++) {
25
+ if (latest.parts[index] > current.parts[index]) return true
26
+ if (latest.parts[index] < current.parts[index]) return false
27
+ }
28
+ if (!latest.prerelease && current.prerelease) return true
29
+ if (latest.prerelease && !current.prerelease) return false
30
+ return Boolean(
31
+ latest.prerelease &&
32
+ current.prerelease &&
33
+ latest.prerelease !== current.prerelease,
34
+ )
35
+ }
36
+
37
+ export function createUpdateChecker({
38
+ fetchImplementation = globalThis.fetch,
39
+ now = () => Date.now(),
40
+ } = {}) {
41
+ let checkedAt = 0
42
+ let cachedResult
43
+ let pendingCheck
44
+
45
+ async function fetchLatest() {
46
+ const controller = new AbortController()
47
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS)
48
+ try {
49
+ const response = await fetchImplementation(
50
+ `${NPM_REGISTRY_URL}/${PACKAGE_NAME}/latest`,
51
+ { signal: controller.signal },
52
+ )
53
+ if (!response.ok) return { state: "unavailable", currentVersion: PLUGIN_VERSION }
54
+ const data = await response.json()
55
+ const latestVersion = typeof data?.version === "string"
56
+ ? data.version.trim()
57
+ : ""
58
+ if (!parseVersion(latestVersion)) {
59
+ return { state: "unavailable", currentVersion: PLUGIN_VERSION }
60
+ }
61
+ if (!isVersionNewer(latestVersion, PLUGIN_VERSION)) {
62
+ return {
63
+ state: "current",
64
+ currentVersion: PLUGIN_VERSION,
65
+ latestVersion,
66
+ }
67
+ }
68
+ return {
69
+ state: "available",
70
+ currentVersion: PLUGIN_VERSION,
71
+ latestVersion,
72
+ updateCommand:
73
+ `opencode plugin ${PACKAGE_NAME}@${latestVersion} -g --force`,
74
+ }
75
+ } catch {
76
+ return { state: "unavailable", currentVersion: PLUGIN_VERSION }
77
+ } finally {
78
+ clearTimeout(timeout)
79
+ }
80
+ }
81
+
82
+ return {
83
+ async check({ force = false } = {}) {
84
+ if (!force && checkedAt && now() - checkedAt < CHECK_INTERVAL_MS) {
85
+ return cachedResult
86
+ }
87
+ if (pendingCheck) return pendingCheck
88
+
89
+ pendingCheck = fetchLatest()
90
+ .then((result) => {
91
+ cachedResult = result
92
+ checkedAt = now()
93
+ return result
94
+ })
95
+ .finally(() => { pendingCheck = undefined })
96
+ return pendingCheck
97
+ },
98
+ }
99
+ }
100
+
101
+ export function formatAgentUpdateInstruction(info) {
102
+ return [
103
+ "[BACKEER UPDATE REQUESTED]",
104
+ `The user explicitly requested the Backeer OpenCode plugin update from v${info.currentVersion} to v${info.latestVersion}.`,
105
+ `Run exactly: ${info.updateCommand}`,
106
+ "Then verify that the global OpenCode config is pinned to the exact new version and that the matching package exists in the OpenCode cache.",
107
+ "Do not read, print, replace, or otherwise expose the Backeer token.",
108
+ "Do not use @latest and do not claim success until both installation and verification pass.",
109
+ "Do not terminate the active OpenCode process; tell the user to restart OpenCode after success.",
110
+ ].join("\n")
111
+ }
112
+
113
+ export function createUpdateNoticeHook(updateChecker) {
114
+ const notifiedSessions = new Set()
115
+
116
+ return async (input, output) => {
117
+ if (notifiedSessions.has(input.sessionID)) return
118
+ notifiedSessions.add(input.sessionID)
119
+
120
+ const info = await updateChecker.check()
121
+ if (info.state !== "available") return
122
+ output.parts.push({
123
+ id: `prt_backeer-update-${Date.now()}`,
124
+ sessionID: input.sessionID,
125
+ messageID: output.message.id,
126
+ type: "text",
127
+ synthetic: true,
128
+ text: [
129
+ "[BACKEER UPDATE AVAILABLE]",
130
+ `Backeer OpenCode plugin v${info.latestVersion} is available; current version is v${info.currentVersion}.`,
131
+ "Inform the user that an update is available and suggest `/backeer update`.",
132
+ "Do not install it solely because of this notice.",
133
+ ].join("\n"),
134
+ })
135
+ }
136
+ }