Skip to content

Commit 1e0e738

Browse files
authored
Merge pull request #98 from Tarquinen/refactor/simplify-version-update
Simplify update mechanism to modify config instead of spawning bun
2 parents 9759988 + e633a53 commit 1e0e738

File tree

5 files changed

+60
-109
lines changed

5 files changed

+60
-109
lines changed

index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,7 @@ const plugin: Plugin = (async (ctx) => {
5353

5454
// Check for updates after a delay
5555
setTimeout(() => {
56-
checkForUpdates(ctx.client, logger, {
57-
showToast: config.showUpdateToasts ?? true,
58-
autoUpdate: config.autoUpdate ?? true
59-
}).catch(() => { })
56+
checkForUpdates(ctx.client, logger).catch(() => { })
6057
}, 5000)
6158

6259
// Show migration toast if there were config migrations

lib/config.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export interface PluginConfig {
1313
protectedTools: string[]
1414
model?: string
1515
showModelErrorToasts?: boolean
16-
showUpdateToasts?: boolean
17-
autoUpdate?: boolean
1816
strictModelSelection?: boolean
1917
pruning_summary: "off" | "minimal" | "detailed"
2018
nudge_freq: number
@@ -34,8 +32,6 @@ const defaultConfig: PluginConfig = {
3432
debug: false,
3533
protectedTools: ['task', 'todowrite', 'todoread', 'prune', 'batch', 'edit', 'write'],
3634
showModelErrorToasts: true,
37-
showUpdateToasts: true,
38-
autoUpdate: true,
3935
strictModelSelection: false,
4036
pruning_summary: 'detailed',
4137
nudge_freq: 10,
@@ -51,8 +47,6 @@ const VALID_CONFIG_KEYS = new Set([
5147
'protectedTools',
5248
'model',
5349
'showModelErrorToasts',
54-
'showUpdateToasts',
55-
'autoUpdate',
5650
'strictModelSelection',
5751
'pruning_summary',
5852
'nudge_freq',
@@ -116,10 +110,6 @@ function createDefaultConfig(): void {
116110
// "model": "anthropic/claude-haiku-4-5",
117111
// Show toast notifications when model selection fails
118112
"showModelErrorToasts": true,
119-
// Show toast notifications when a new version is available
120-
"showUpdateToasts": true,
121-
// Automatically update to new versions (restart required to apply)
122-
"autoUpdate": true,
123113
// Only run AI analysis with session model or configured model (disables fallback models)
124114
"strictModelSelection": false,
125115
// AI analysis strategies (deduplication runs automatically on every request)
@@ -209,8 +199,6 @@ export function getConfig(ctx?: PluginInput): ConfigResult {
209199
protectedTools: [...new Set([...config.protectedTools, ...(globalConfig.protectedTools ?? [])])],
210200
model: globalConfig.model ?? config.model,
211201
showModelErrorToasts: globalConfig.showModelErrorToasts ?? config.showModelErrorToasts,
212-
showUpdateToasts: globalConfig.showUpdateToasts ?? config.showUpdateToasts,
213-
autoUpdate: globalConfig.autoUpdate ?? config.autoUpdate,
214202
strictModelSelection: globalConfig.strictModelSelection ?? config.strictModelSelection,
215203
strategies: mergeStrategies(config.strategies, globalConfig.strategies as any),
216204
pruning_summary: globalConfig.pruning_summary ?? config.pruning_summary,
@@ -242,8 +230,6 @@ export function getConfig(ctx?: PluginInput): ConfigResult {
242230
protectedTools: [...new Set([...config.protectedTools, ...(projectConfig.protectedTools ?? [])])],
243231
model: projectConfig.model ?? config.model,
244232
showModelErrorToasts: projectConfig.showModelErrorToasts ?? config.showModelErrorToasts,
245-
showUpdateToasts: projectConfig.showUpdateToasts ?? config.showUpdateToasts,
246-
autoUpdate: projectConfig.autoUpdate ?? config.autoUpdate,
247233
strictModelSelection: projectConfig.strictModelSelection ?? config.strictModelSelection,
248234
strategies: mergeStrategies(config.strategies, projectConfig.strategies as any),
249235
pruning_summary: projectConfig.pruning_summary ?? config.pruning_summary,

lib/version-checker.ts

Lines changed: 56 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { readFileSync } from 'fs'
1+
import { readFileSync, writeFileSync, existsSync } from 'fs'
22
import { join, dirname } from 'path'
33
import { fileURLToPath } from 'url'
4-
import { spawn } from 'child_process'
54
import { homedir } from 'os'
65

76
export const PACKAGE_NAME = '@tarquinen/opencode-dcp'
@@ -12,14 +11,12 @@ const __dirname = dirname(__filename)
1211

1312
export function getLocalVersion(): string {
1413
try {
15-
// Walk up from the current module to find the project's package.json
16-
// This works whether running from dist/lib/, lib/, or installed in node_modules
1714
let dir = __dirname
1815
for (let i = 0; i < 5; i++) {
1916
const pkgPath = join(dir, 'package.json')
2017
try {
2118
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
22-
if (pkg.name === '@tarquinen/opencode-dcp') {
19+
if (pkg.name === PACKAGE_NAME) {
2320
return pkg.version
2421
}
2522
} catch {
@@ -65,75 +62,55 @@ export function isOutdated(local: string, remote: string): boolean {
6562
return false
6663
}
6764

68-
export async function performUpdate(targetVersion: string, logger?: { info: (component: string, message: string, data?: any) => void }): Promise<boolean> {
69-
const cacheDir = join(homedir(), '.cache', 'opencode')
70-
const bunCacheDir = join(homedir(), '.bun', 'install', 'cache', '@tarquinen')
71-
const packageSpec = `${PACKAGE_NAME}@${targetVersion}`
65+
/**
66+
* Updates config files to pin the new version.
67+
* Checks both global and local project configs.
68+
* Handles: "@tarquinen/opencode-dcp", "@tarquinen/opencode-dcp@latest", "@tarquinen/[email protected]"
69+
*/
70+
export function updateConfigVersion(newVersion: string, logger?: { info: (component: string, message: string, data?: any) => void }): boolean {
71+
const configs = [
72+
join(homedir(), '.config', 'opencode', 'opencode.jsonc'), // Global
73+
join(process.cwd(), '.opencode', 'opencode.jsonc') // Local project
74+
]
7275

73-
logger?.info("version", "Starting auto-update", { targetVersion, cacheDir })
76+
let anyUpdated = false
7477

75-
// Clear bun's install cache for this package to prevent stale versions
76-
try {
77-
const { rmSync } = await import('fs')
78-
rmSync(bunCacheDir, { recursive: true, force: true })
79-
logger?.info("version", "Cleared bun cache", { bunCacheDir })
80-
} catch (err) {
81-
logger?.info("version", "Could not clear bun cache", { error: (err as Error).message })
82-
}
83-
84-
return new Promise((resolve) => {
85-
let resolved = false
78+
for (const configPath of configs) {
79+
try {
80+
if (!existsSync(configPath)) continue
8681

87-
// Use bun since opencode uses bun to manage its plugin dependencies
88-
const proc = spawn('bun', ['add', packageSpec], {
89-
cwd: cacheDir,
90-
stdio: 'pipe'
91-
})
82+
const content = readFileSync(configPath, 'utf-8')
9283

93-
let stderr = ''
94-
proc.stderr?.on('data', (data) => {
95-
stderr += data.toString()
96-
})
84+
// Match @tarquinen/opencode-dcp with optional version suffix (latest, 1.2.3, etc)
85+
// The regex matches: " @tarquinen/opencode-dcp (optional @anything) "
86+
const regex = new RegExp(`"${PACKAGE_NAME}(@[^"]*)?"`,'g')
87+
const newEntry = `"${PACKAGE_NAME}@${newVersion}"`
9788

98-
proc.on('close', (code) => {
99-
if (resolved) return
100-
resolved = true
101-
clearTimeout(timeoutId)
102-
if (code === 0) {
103-
logger?.info("version", "Auto-update succeeded", { targetVersion })
104-
resolve(true)
105-
} else {
106-
logger?.info("version", "Auto-update failed", { targetVersion, code, stderr: stderr.slice(0, 500) })
107-
resolve(false)
89+
if (!regex.test(content)) {
90+
continue
10891
}
109-
})
11092

111-
proc.on('error', (err) => {
112-
if (resolved) return
113-
resolved = true
114-
clearTimeout(timeoutId)
115-
logger?.info("version", "Auto-update error", { targetVersion, error: err.message })
116-
resolve(false)
117-
})
93+
// Reset regex state
94+
regex.lastIndex = 0
95+
const updatedContent = content.replace(regex, newEntry)
96+
97+
if (updatedContent !== content) {
98+
writeFileSync(configPath, updatedContent, 'utf-8')
99+
logger?.info("version", "Config updated", { configPath, newVersion })
100+
anyUpdated = true
101+
}
102+
} catch (err) {
103+
logger?.info("version", "Failed to update config", { configPath, error: (err as Error).message })
104+
}
105+
}
118106

119-
// Timeout after 60 seconds
120-
const timeoutId = setTimeout(() => {
121-
if (resolved) return
122-
resolved = true
123-
proc.kill()
124-
logger?.info("version", "Auto-update timed out", { targetVersion })
125-
resolve(false)
126-
}, 60000)
127-
})
107+
return anyUpdated
128108
}
129109

130110
export async function checkForUpdates(
131111
client: any,
132-
logger?: { info: (component: string, message: string, data?: any) => void },
133-
options: { showToast?: boolean; autoUpdate?: boolean } = {}
112+
logger?: { info: (component: string, message: string, data?: any) => void }
134113
): Promise<void> {
135-
const { showToast = true, autoUpdate = false } = options
136-
137114
try {
138115
const local = getLocalVersion()
139116
const npm = await getNpmVersion()
@@ -148,41 +125,32 @@ export async function checkForUpdates(
148125
return
149126
}
150127

151-
logger?.info("version", "Update available", { local, npm, autoUpdate })
152-
153-
if (autoUpdate) {
154-
// Attempt auto-update
155-
const success = await performUpdate(npm, logger)
156-
157-
if (success && showToast) {
158-
await client.tui.showToast({
159-
body: {
160-
title: "DCP: Updated!",
161-
message: `v${local} → v${npm}\nRestart OpenCode to apply`,
162-
variant: "success",
163-
duration: 6000
164-
}
165-
})
166-
} else if (!success && showToast) {
167-
await client.tui.showToast({
168-
body: {
169-
title: "DCP: Update failed",
170-
message: `v${local} → v${npm}\nManual: npm install ${PACKAGE_NAME}@${npm}`,
171-
variant: "warning",
172-
duration: 6000
173-
}
174-
})
175-
}
176-
} else if (showToast) {
128+
logger?.info("version", "Update available", { local, npm })
129+
130+
// Update any configs found
131+
const updated = updateConfigVersion(npm, logger)
132+
133+
if (updated) {
177134
await client.tui.showToast({
178135
body: {
179136
title: "DCP: Update available",
180-
message: `v${local} → v${npm}`,
137+
message: `v${local} → v${npm}\nRestart OpenCode to apply`,
181138
variant: "info",
182139
duration: 6000
183140
}
184141
})
142+
} else {
143+
// Config update failed or plugin not found in config, show manual instructions
144+
await client.tui.showToast({
145+
body: {
146+
title: "DCP: Update available",
147+
message: `v${local} → v${npm}\nUpdate opencode.jsonc:\n"${PACKAGE_NAME}@${npm}"`,
148+
variant: "info",
149+
duration: 8000
150+
}
151+
})
185152
}
186153
} catch {
154+
// Silently fail version checks
187155
}
188156
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tarquinen/opencode-dcp",
4-
"version": "0.4.8",
4+
"version": "0.4.9",
55
"type": "module",
66
"description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context",
77
"main": "./dist/index.js",

0 commit comments

Comments
 (0)