@comfanion/workflow 4.36.18 → 4.36.20
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/bin/cli.js +5 -4
- package/package.json +1 -1
- package/src/build-info.json +2 -2
- package/src/opencode/plugins/README.md +18 -0
- package/src/opencode/plugins/version-check.ts +189 -0
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import inquirer from 'inquirer';
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import path from 'path';
|
|
9
|
+
import crypto from 'crypto';
|
|
9
10
|
import { fileURLToPath } from 'url';
|
|
10
11
|
import { execSync } from 'child_process';
|
|
11
12
|
import yaml from 'js-yaml';
|
|
@@ -293,7 +294,7 @@ program
|
|
|
293
294
|
const oldPkgPath = path.join(vectorizerDir, 'package.json');
|
|
294
295
|
if (await fs.pathExists(oldPkgPath)) {
|
|
295
296
|
const oldPkg = await fs.readFile(oldPkgPath, 'utf8');
|
|
296
|
-
oldPkgHash =
|
|
297
|
+
oldPkgHash = crypto.createHash('md5').update(oldPkg).digest('hex');
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
// Read existing config.yaml for merge
|
|
@@ -342,7 +343,7 @@ program
|
|
|
342
343
|
// Check if package.json changed
|
|
343
344
|
const newPkgPath = path.join(newVectorizerDir, 'package.json');
|
|
344
345
|
const newPkg = await fs.readFile(newPkgPath, 'utf8');
|
|
345
|
-
const newPkgHash =
|
|
346
|
+
const newPkgHash = crypto.createHash('md5').update(newPkg).digest('hex');
|
|
346
347
|
|
|
347
348
|
if (hadVectorizerModules && oldPkgHash === newPkgHash) {
|
|
348
349
|
// Dependencies unchanged - restore cached node_modules
|
|
@@ -620,7 +621,7 @@ program
|
|
|
620
621
|
const oldPkgPath = path.join(vectorizerDir, 'package.json');
|
|
621
622
|
if (await fs.pathExists(oldPkgPath)) {
|
|
622
623
|
const oldPkg = await fs.readFile(oldPkgPath, 'utf8');
|
|
623
|
-
oldPkgHash =
|
|
624
|
+
oldPkgHash = crypto.createHash('md5').update(oldPkg).digest('hex');
|
|
624
625
|
}
|
|
625
626
|
|
|
626
627
|
// Create full backup (unless --no-backup)
|
|
@@ -667,7 +668,7 @@ program
|
|
|
667
668
|
// Check if package.json changed
|
|
668
669
|
const newPkgPath = path.join(newVectorizerDir, 'package.json');
|
|
669
670
|
const newPkg = await fs.readFile(newPkgPath, 'utf8');
|
|
670
|
-
const newPkgHash =
|
|
671
|
+
const newPkgHash = crypto.createHash('md5').update(newPkg).digest('hex');
|
|
671
672
|
|
|
672
673
|
if (hasVectorizerModules && oldPkgHash === newPkgHash) {
|
|
673
674
|
// Dependencies unchanged - restore cached node_modules
|
package/package.json
CHANGED
package/src/build-info.json
CHANGED
|
@@ -32,6 +32,24 @@ npx opencode-workflow index --index code
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
+
### version-check.ts
|
|
36
|
+
|
|
37
|
+
Checks for updates and shows a toast notification if a newer version is available.
|
|
38
|
+
|
|
39
|
+
**Features:**
|
|
40
|
+
- Checks npm registry for latest @comfanion/workflow version
|
|
41
|
+
- Compares with locally installed version
|
|
42
|
+
- Shows toast notification if update available
|
|
43
|
+
- Caches check result for 24 hours (avoids spam)
|
|
44
|
+
- Supports EN/UK/RU languages
|
|
45
|
+
|
|
46
|
+
**Toast Example:**
|
|
47
|
+
```
|
|
48
|
+
🚀 Update available! 4.36.19 → 4.37.0. Run: npx @comfanion/workflow update
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
35
53
|
### custom-compaction.ts
|
|
36
54
|
|
|
37
55
|
Intelligent session compaction that preserves flow context.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import fs from "fs/promises"
|
|
4
|
+
import https from "https"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Version Check Plugin
|
|
8
|
+
*
|
|
9
|
+
* Checks if a newer version of @comfanion/workflow is available on npm.
|
|
10
|
+
* Shows a toast notification if an update is available.
|
|
11
|
+
*
|
|
12
|
+
* Configuration in .opencode/config.yaml:
|
|
13
|
+
* version_check:
|
|
14
|
+
* enabled: true # Enable version checking
|
|
15
|
+
* check_interval: 86400000 # Check once per day (ms)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const DEBUG = process.env.DEBUG?.includes('version-check') || process.env.DEBUG === '*'
|
|
19
|
+
const PACKAGE_NAME = '@comfanion/workflow'
|
|
20
|
+
const CACHE_FILE = '.version-check-cache.json'
|
|
21
|
+
|
|
22
|
+
function log(msg: string): void {
|
|
23
|
+
if (DEBUG) console.log(`[version-check] ${msg}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface VersionCache {
|
|
27
|
+
lastCheck: number
|
|
28
|
+
latestVersion: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getLocalVersion(directory: string): Promise<string | null> {
|
|
32
|
+
try {
|
|
33
|
+
// Try build-info.json first (created during npm publish)
|
|
34
|
+
const buildInfoPath = path.join(directory, '.opencode', 'build-info.json')
|
|
35
|
+
const buildInfo = JSON.parse(await fs.readFile(buildInfoPath, 'utf8'))
|
|
36
|
+
if (buildInfo.version) return buildInfo.version
|
|
37
|
+
} catch {}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Fallback to config.yaml version field
|
|
41
|
+
const configPath = path.join(directory, '.opencode', 'config.yaml')
|
|
42
|
+
const config = await fs.readFile(configPath, 'utf8')
|
|
43
|
+
const match = config.match(/^version:\s*["']?([\d.]+)["']?/m)
|
|
44
|
+
if (match) return match[1]
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function getLatestVersion(): Promise<string | null> {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const timeout = setTimeout(() => resolve(null), 5000) // 5s timeout
|
|
53
|
+
|
|
54
|
+
https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, (res) => {
|
|
55
|
+
let data = ''
|
|
56
|
+
res.on('data', chunk => data += chunk)
|
|
57
|
+
res.on('end', () => {
|
|
58
|
+
clearTimeout(timeout)
|
|
59
|
+
try {
|
|
60
|
+
const json = JSON.parse(data)
|
|
61
|
+
resolve(json.version || null)
|
|
62
|
+
} catch {
|
|
63
|
+
resolve(null)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}).on('error', () => {
|
|
67
|
+
clearTimeout(timeout)
|
|
68
|
+
resolve(null)
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function loadCache(directory: string): Promise<VersionCache | null> {
|
|
74
|
+
try {
|
|
75
|
+
const cachePath = path.join(directory, '.opencode', CACHE_FILE)
|
|
76
|
+
const data = await fs.readFile(cachePath, 'utf8')
|
|
77
|
+
return JSON.parse(data)
|
|
78
|
+
} catch {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function saveCache(directory: string, cache: VersionCache): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
const cachePath = path.join(directory, '.opencode', CACHE_FILE)
|
|
86
|
+
await fs.writeFile(cachePath, JSON.stringify(cache))
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function compareVersions(local: string, latest: string): number {
|
|
91
|
+
const localParts = local.split('.').map(Number)
|
|
92
|
+
const latestParts = latest.split('.').map(Number)
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < 3; i++) {
|
|
95
|
+
const l = localParts[i] || 0
|
|
96
|
+
const r = latestParts[i] || 0
|
|
97
|
+
if (l < r) return -1
|
|
98
|
+
if (l > r) return 1
|
|
99
|
+
}
|
|
100
|
+
return 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fun update messages
|
|
104
|
+
const UPDATE_MESSAGES = {
|
|
105
|
+
en: (local: string, latest: string) => `🚀 Update available! ${local} → ${latest}. Run: npx @comfanion/workflow update`,
|
|
106
|
+
uk: (local: string, latest: string) => `🚀 Є оновлення! ${local} → ${latest}. Виконай: npx @comfanion/workflow update`,
|
|
107
|
+
ru: (local: string, latest: string) => `🚀 Доступно обновление! ${local} → ${latest}. Выполни: npx @comfanion/workflow update`,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function getLanguage(directory: string): Promise<'en' | 'uk' | 'ru'> {
|
|
111
|
+
try {
|
|
112
|
+
const configPath = path.join(directory, '.opencode', 'config.yaml')
|
|
113
|
+
const config = await fs.readFile(configPath, 'utf8')
|
|
114
|
+
if (config.includes('communication_language: Ukrainian') || config.includes('communication_language: uk')) return 'uk'
|
|
115
|
+
if (config.includes('communication_language: Russian') || config.includes('communication_language: ru')) return 'ru'
|
|
116
|
+
} catch {}
|
|
117
|
+
return 'en'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const VersionCheckPlugin: Plugin = async ({ directory, client }) => {
|
|
121
|
+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000 // 24 hours
|
|
122
|
+
|
|
123
|
+
const toast = async (message: string, variant: 'info' | 'success' | 'error' = 'info') => {
|
|
124
|
+
try {
|
|
125
|
+
await client?.tui?.showToast?.({ body: { message, variant } })
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
log(`Plugin loaded`)
|
|
130
|
+
|
|
131
|
+
// Run check after short delay (let TUI initialize)
|
|
132
|
+
setTimeout(async () => {
|
|
133
|
+
try {
|
|
134
|
+
// Check cache first
|
|
135
|
+
const cache = await loadCache(directory)
|
|
136
|
+
const now = Date.now()
|
|
137
|
+
|
|
138
|
+
// Skip if checked recently
|
|
139
|
+
if (cache && (now - cache.lastCheck) < CHECK_INTERVAL) {
|
|
140
|
+
log(`Skipping check (cached ${Math.round((now - cache.lastCheck) / 1000 / 60)}min ago)`)
|
|
141
|
+
|
|
142
|
+
// But still show toast if update was available
|
|
143
|
+
const localVersion = await getLocalVersion(directory)
|
|
144
|
+
if (localVersion && cache.latestVersion && compareVersions(localVersion, cache.latestVersion) < 0) {
|
|
145
|
+
const lang = await getLanguage(directory)
|
|
146
|
+
await toast(UPDATE_MESSAGES[lang](localVersion, cache.latestVersion), 'info')
|
|
147
|
+
}
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get versions
|
|
152
|
+
const localVersion = await getLocalVersion(directory)
|
|
153
|
+
if (!localVersion) {
|
|
154
|
+
log(`Could not determine local version`)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log(`Local version: ${localVersion}`)
|
|
159
|
+
|
|
160
|
+
const latestVersion = await getLatestVersion()
|
|
161
|
+
if (!latestVersion) {
|
|
162
|
+
log(`Could not fetch latest version from npm`)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
log(`Latest version: ${latestVersion}`)
|
|
167
|
+
|
|
168
|
+
// Save to cache
|
|
169
|
+
await saveCache(directory, { lastCheck: now, latestVersion })
|
|
170
|
+
|
|
171
|
+
// Compare and notify
|
|
172
|
+
if (compareVersions(localVersion, latestVersion) < 0) {
|
|
173
|
+
log(`Update available: ${localVersion} → ${latestVersion}`)
|
|
174
|
+
const lang = await getLanguage(directory)
|
|
175
|
+
await toast(UPDATE_MESSAGES[lang](localVersion, latestVersion), 'info')
|
|
176
|
+
} else {
|
|
177
|
+
log(`Up to date!`)
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
log(`Error: ${(e as Error).message}`)
|
|
181
|
+
}
|
|
182
|
+
}, 2000)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
event: async () => {}, // No events needed
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default VersionCheckPlugin
|