@comfanion/workflow 4.36.19 → 4.36.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.36.19",
3
+ "version": "4.36.21",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "3.0.0",
3
- "buildDate": "2026-01-24T16:08:15.479Z",
2
+ "version": "4.36.21",
3
+ "buildDate": "2026-01-24T17:35:27.690Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -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 1 hour
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: 3600000 # Check once per hour (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 = 60 * 60 * 1000 // 1 hour (you release often! 🚀)
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